Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-ora2
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-ora2
Commits
aa908378
Commit
aa908378
authored
Aug 25, 2015
by
Will Daly
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Upgrade djangorestframework to v3.1
parent
2f24291e
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
80 additions
and
93 deletions
+80
-93
openassessment/assessment/serializers/base.py
+46
-45
openassessment/assessment/test/test_peer.py
+2
-1
openassessment/assessment/test/test_training.py
+2
-3
openassessment/tests/data/db_fixtures/feedback_on_assessment.json
+3
-4
openassessment/tests/data/db_fixtures/feedback_only_criterion.json
+3
-4
openassessment/tests/data/db_fixtures/peer_assessed.json
+3
-4
openassessment/tests/data/db_fixtures/scored.json
+3
-4
openassessment/tests/data/db_fixtures/self_assessed.json
+2
-2
openassessment/tests/data/db_fixtures/submitted.json
+2
-3
openassessment/tests/data/db_fixtures/unicode.json
+2
-2
openassessment/workflow/serializers.py
+2
-16
openassessment/xblock/data_conversion.py
+2
-2
openassessment/xblock/test/test_validation.py
+5
-1
requirements/base.txt
+3
-2
No files found.
openassessment/assessment/serializers/base.py
View file @
aa908378
...
...
@@ -7,6 +7,7 @@ import logging
from
django.core.cache
import
cache
from
rest_framework
import
serializers
from
rest_framework.fields
import
IntegerField
,
DateTimeField
from
openassessment.assessment.models
import
(
Assessment
,
AssessmentPart
,
Criterion
,
CriterionOption
,
Rubric
,
)
...
...
@@ -22,75 +23,41 @@ class InvalidRubric(Exception):
self
.
errors
=
deepcopy
(
errors
)
class
NestedModelSerializer
(
serializers
.
ModelSerializer
):
"""Model Serializer that supports deserialization with arbitrary nesting.
The Django REST Framework does not currently support deserialization more
than one level deep (so a parent and children). We want to be able to
create a :class:`Rubric` → :class:`Criterion` → :class:`CriterionOption`
hierarchy.
Much of the base logic already "just works" and serialization of arbritrary
depth is supported. So we just override the save_object method to
recursively link foreign key relations instead of doing it one level deep.
We don't touch many-to-many relationships because we don't need to for our
purposes, so those still only work one level deep.
"""
def
recursively_link_related
(
self
,
obj
,
**
kwargs
):
if
getattr
(
obj
,
'_related_data'
,
None
):
for
accessor_name
,
related
in
obj
.
_related_data
.
items
():
setattr
(
obj
,
accessor_name
,
related
)
for
related_obj
in
related
:
self
.
recursively_link_related
(
related_obj
,
**
kwargs
)
del
(
obj
.
_related_data
)
def
save_object
(
self
,
obj
,
**
kwargs
):
obj
.
save
(
**
kwargs
)
# The code for many-to-many relationships is just copy-pasted from the
# Django REST Framework ModelSerializer
if
getattr
(
obj
,
'_m2m_data'
,
None
):
for
accessor_name
,
object_list
in
obj
.
_m2m_data
.
items
():
setattr
(
obj
,
accessor_name
,
object_list
)
del
(
obj
.
_m2m_data
)
# This is our only real change from ModelSerializer
self
.
recursively_link_related
(
obj
,
**
kwargs
)
class
CriterionOptionSerializer
(
serializers
.
ModelSerializer
):
"""Serializer for :class:`CriterionOption`"""
# Django Rest Framework v3 no longer requires `PositiveIntegerField`s
# to be positive by default, so we need to explicitly set the `min_value`
# on the serializer field.
points
=
IntegerField
(
min_value
=
0
)
class
CriterionOptionSerializer
(
NestedModelSerializer
):
"""Serializer for :class:`CriterionOption`"""
class
Meta
:
model
=
CriterionOption
fields
=
(
'order_num'
,
'points'
,
'name'
,
'label'
,
'explanation'
)
class
CriterionSerializer
(
Nested
ModelSerializer
):
class
CriterionSerializer
(
serializers
.
ModelSerializer
):
"""Serializer for :class:`Criterion`"""
options
=
CriterionOptionSerializer
(
required
=
True
,
many
=
True
)
points_possible
=
serializers
.
Field
(
source
=
'points_possible'
)
class
Meta
:
model
=
Criterion
fields
=
(
'order_num'
,
'name'
,
'label'
,
'prompt'
,
'options'
,
'points_possible'
)
class
RubricSerializer
(
Nested
ModelSerializer
):
class
RubricSerializer
(
serializers
.
ModelSerializer
):
"""Serializer for :class:`Rubric`."""
criteria
=
CriterionSerializer
(
required
=
True
,
many
=
True
)
points_possible
=
serializers
.
Field
(
source
=
'points_possible'
)
class
Meta
:
model
=
Rubric
fields
=
(
'id'
,
'content_hash'
,
'structure_hash'
,
'criteria'
,
'points_possible'
)
def
validate_criteria
(
self
,
attrs
,
sourc
e
):
def
validate_criteria
(
self
,
valu
e
):
"""Make sure we have at least one Criterion in the Rubric."""
criteria
=
attrs
[
source
]
if
not
criteria
:
if
not
value
:
raise
serializers
.
ValidationError
(
"Must have at least one criterion"
)
return
attrs
return
value
@classmethod
def
serialized_from_cache
(
cls
,
rubric
,
local_cache
=
None
):
...
...
@@ -135,6 +102,33 @@ class RubricSerializer(NestedModelSerializer):
return
rubric_dict
def
create
(
self
,
validated_data
):
"""
Create the rubric model, including its nested models.
Args:
validated_data (dict): Dictionary of validated data for the rubric,
including nested Criterion and CriterionOption data.
Returns:
Rubric
"""
criteria_data
=
validated_data
.
pop
(
"criteria"
)
rubric
=
Rubric
.
objects
.
create
(
**
validated_data
)
# Create each nested criterion in the rubric, linking it to the rubric
for
criterion_dict
in
criteria_data
:
options_data
=
criterion_dict
.
pop
(
"options"
)
criterion
=
Criterion
.
objects
.
create
(
rubric
=
rubric
,
**
criterion_dict
)
# Create each option in the criterion, linking it to the criterion
CriterionOption
.
objects
.
bulk_create
(
CriterionOption
(
criterion
=
criterion
,
**
option_dict
)
for
option_dict
in
options_data
)
return
rubric
class
AssessmentPartSerializer
(
serializers
.
ModelSerializer
):
"""Serializer for :class:`AssessmentPart`."""
...
...
@@ -147,6 +141,13 @@ class AssessmentPartSerializer(serializers.ModelSerializer):
class
AssessmentSerializer
(
serializers
.
ModelSerializer
):
"""Simplified serializer for :class:`Assessment` that's lighter on the DB."""
# Django Rest Framework v3 uses the Django setting `DATETIME_FORMAT`
# when serializing datetimes. This differs from v2, which always
# returned a datetime. To preserve the old behavior, we explicitly
# set `format` to None.
# http://www.django-rest-framework.org/api-guide/fields/#datetimefield
scored_at
=
DateTimeField
(
format
=
None
,
required
=
False
)
class
Meta
:
model
=
Assessment
fields
=
(
...
...
openassessment/assessment/test/test_peer.py
View file @
aa908378
...
...
@@ -152,13 +152,14 @@ STEP_REQUIREMENTS = {
}
}
@ddt
class
TestPeerApi
(
CacheResetTest
):
"""
Tests for the peer assessment API functions.
"""
CREATE_ASSESSMENT_NUM_QUERIES
=
5
8
CREATE_ASSESSMENT_NUM_QUERIES
=
5
3
def
test_create_assessment_points
(
self
):
self
.
_create_student_and_submission
(
"Tim"
,
"Tim's answer"
)
...
...
openassessment/assessment/test/test_training.py
View file @
aa908378
...
...
@@ -32,8 +32,8 @@ class TrainingExampleSerializerTest(CacheResetTest):
},
{
"order_num"
:
2
,
"name"
:
"єχ¢єℓℓєηт"
,
"explanation"
:
"乇メc乇レレ乇刀イ フo乃!"
,
"name"
:
u
"єχ¢єℓℓєηт"
,
"explanation"
:
u
"乇メc乇レレ乇刀イ フo乃!"
,
"points"
:
2
,
},
]
...
...
@@ -89,7 +89,6 @@ class TrainingExampleSerializerTest(CacheResetTest):
},
]
def
test_duplicate_training_example
(
self
):
# Deserialize some examples for a rubric
deserialize_training_examples
(
self
.
EXAMPLES
[
0
:
2
],
self
.
RUBRIC
)
...
...
openassessment/tests/data/db_fixtures/feedback_on_assessment.json
View file @
aa908378
...
...
@@ -780,7 +780,7 @@
"uuid"
:
"387d840a-d0ae-11e3-bb0e-14109fd8dc43"
,
"student_item"
:
2
,
"created_at"
:
"2014-04-30T21:27:24.181Z"
,
"
raw_
answer"
:
"{
\"
text
\"
:
\"
E
\\
u00e3
\\
u00e9um quis d
\\
u00ed
\\
u00e7o
\\
u00e9l
\\
u00e2b
\\
u00f3r
\\
u00e3ret. N
\\
u00e1m in
\\
u00e7
\\
u00f4mmune p
\\
u00f3nder
\\
u00fam apeirian, te
\\
u00e2lii m
\\
u00e1zim
\\
u00ednt
\\
u00e9ll
\\
u00eagat nec, ex
\\
u00e9leif
\\
u00e9nd el
\\
u00f3quenti
\\
u00e2m usu. His no n
\\
u00f3vum lu
\\
u00e7ilius,
\\
u00e0utem
\\
u00e3
\\
u00e9que vix
\\
u00e0d.
\"
}"
,
"answer"
:
"{
\"
text
\"
:
\"
E
\\
u00e3
\\
u00e9um quis d
\\
u00ed
\\
u00e7o
\\
u00e9l
\\
u00e2b
\\
u00f3r
\\
u00e3ret. N
\\
u00e1m in
\\
u00e7
\\
u00f4mmune p
\\
u00f3nder
\\
u00fam apeirian, te
\\
u00e2lii m
\\
u00e1zim
\\
u00ednt
\\
u00e9ll
\\
u00eagat nec, ex
\\
u00e9leif
\\
u00e9nd el
\\
u00f3quenti
\\
u00e2m usu. His no n
\\
u00f3vum lu
\\
u00e7ilius,
\\
u00e0utem
\\
u00e3
\\
u00e9que vix
\\
u00e0d.
\"
}"
,
"attempt_number"
:
1
,
"submitted_at"
:
"2014-04-30T21:27:24.173Z"
}
...
...
@@ -792,7 +792,7 @@
"uuid"
:
"1783758f-d0ae-11e3-b495-14109fd8dc43"
,
"student_item"
:
1
,
"created_at"
:
"2014-04-30T21:26:28.855Z"
,
"
raw_
answer"
:
"{
\"
text
\"
:
\"
L
\\
u00f5r
\\
u00eam
\\
u00edpsum d
\\
u00f4lor sit amet, in d
\\
u00fao p
\\
u00f5pul
\\
u00f3 m
\\
u00e0nd
\\
u00e1mus, alienum cons
\\
u00e9q
\\
u00faat pers
\\
u00e9cuti mel
\\
u00e0n. R
\\
u00eabum de
\\
u00e7or
\\
u00ea
\\
u00e9
\\
u00fam an, s
\\
u00e0ep
\\
u00e9 p
\\
u00f5pulo splendid
\\
u00e9 te p
\\
u00e9r.
\"
}"
,
"answer"
:
"{
\"
text
\"
:
\"
L
\\
u00f5r
\\
u00eam
\\
u00edpsum d
\\
u00f4lor sit amet, in d
\\
u00fao p
\\
u00f5pul
\\
u00f3 m
\\
u00e0nd
\\
u00e1mus, alienum cons
\\
u00e9q
\\
u00faat pers
\\
u00e9cuti mel
\\
u00e0n. R
\\
u00eabum de
\\
u00e7or
\\
u00ea
\\
u00e9
\\
u00fam an, s
\\
u00e0ep
\\
u00e9 p
\\
u00f5pulo splendid
\\
u00e9 te p
\\
u00e9r.
\"
}"
,
"attempt_number"
:
1
,
"submitted_at"
:
"2014-04-30T21:26:28.847Z"
}
...
...
@@ -839,4 +839,4 @@
"latest"
:
2
}
}
]
\ No newline at end of file
]
openassessment/tests/data/db_fixtures/feedback_only_criterion.json
View file @
aa908378
...
...
@@ -821,7 +821,7 @@
"uuid"
:
"28cebeca-d0ab-11e3-a6ab-14109fd8dc43"
,
"student_item"
:
2
,
"created_at"
:
"2014-04-30T21:05:29.380Z"
,
"
raw_
answer"
:
"{
\"
text
\"
:
\"
Etiam vel neque id nunc lacinia tincidunt.
\"
}"
,
"answer"
:
"{
\"
text
\"
:
\"
Etiam vel neque id nunc lacinia tincidunt.
\"
}"
,
"attempt_number"
:
1
,
"submitted_at"
:
"2014-04-30T21:05:29.372Z"
}
...
...
@@ -833,9 +833,9 @@
"uuid"
:
"cf5190b8-d0aa-11e3-a734-14109fd8dc43"
,
"student_item"
:
1
,
"created_at"
:
"2014-04-30T21:02:59.241Z"
,
"
raw_
answer"
:
"{
\"
text
\"
:
\"
Lorem ipsum dolor sit amet
\"
}"
,
"answer"
:
"{
\"
text
\"
:
\"
Lorem ipsum dolor sit amet
\"
}"
,
"attempt_number"
:
1
,
"submitted_at"
:
"2014-04-30T21:02:59.234Z"
}
}
]
\ No newline at end of file
]
openassessment/tests/data/db_fixtures/peer_assessed.json
View file @
aa908378
...
...
@@ -800,7 +800,7 @@
"uuid"
:
"28cebeca-d0ab-11e3-a6ab-14109fd8dc43"
,
"student_item"
:
2
,
"created_at"
:
"2014-04-30T21:05:29.380Z"
,
"
raw_
answer"
:
"{
\"
text
\"
:
\"
Etiam vel neque id nunc lacinia tincidunt.
\"
}"
,
"answer"
:
"{
\"
text
\"
:
\"
Etiam vel neque id nunc lacinia tincidunt.
\"
}"
,
"attempt_number"
:
1
,
"submitted_at"
:
"2014-04-30T21:05:29.372Z"
}
...
...
@@ -812,9 +812,9 @@
"uuid"
:
"cf5190b8-d0aa-11e3-a734-14109fd8dc43"
,
"student_item"
:
1
,
"created_at"
:
"2014-04-30T21:02:59.241Z"
,
"
raw_
answer"
:
"{
\"
text
\"
:
\"
Lorem ipsum dolor sit amet
\"
}"
,
"answer"
:
"{
\"
text
\"
:
\"
Lorem ipsum dolor sit amet
\"
}"
,
"attempt_number"
:
1
,
"submitted_at"
:
"2014-04-30T21:02:59.234Z"
}
}
]
\ No newline at end of file
]
openassessment/tests/data/db_fixtures/scored.json
View file @
aa908378
...
...
@@ -938,7 +938,7 @@
"uuid"
:
"28cebeca-d0ab-11e3-a6ab-14109fd8dc43"
,
"student_item"
:
2
,
"created_at"
:
"2014-04-30T21:05:29.380Z"
,
"
raw_
answer"
:
"{
\"
text
\"
:
\"
Etiam vel neque id nunc lacinia tincidunt.
\"
}"
,
"answer"
:
"{
\"
text
\"
:
\"
Etiam vel neque id nunc lacinia tincidunt.
\"
}"
,
"attempt_number"
:
1
,
"submitted_at"
:
"2014-04-30T21:05:29.372Z"
}
...
...
@@ -950,7 +950,7 @@
"uuid"
:
"cf5190b8-d0aa-11e3-a734-14109fd8dc43"
,
"student_item"
:
1
,
"created_at"
:
"2014-04-30T21:02:59.241Z"
,
"
raw_
answer"
:
"{
\"
text
\"
:
\"
Lorem ipsum dolor sit amet
\"
}"
,
"answer"
:
"{
\"
text
\"
:
\"
Lorem ipsum dolor sit amet
\"
}"
,
"attempt_number"
:
1
,
"submitted_at"
:
"2014-04-30T21:02:59.234Z"
}
...
...
@@ -997,4 +997,4 @@
"latest"
:
2
}
}
]
\ No newline at end of file
]
openassessment/tests/data/db_fixtures/self_assessed.json
View file @
aa908378
...
...
@@ -842,7 +842,7 @@
"uuid"
:
"28cebeca-d0ab-11e3-a6ab-14109fd8dc43"
,
"student_item"
:
2
,
"created_at"
:
"2014-04-30T21:05:29.380Z"
,
"
raw_
answer"
:
"{
\"
text
\"
:
\"
Etiam vel neque id nunc lacinia tincidunt.
\"
}"
,
"answer"
:
"{
\"
text
\"
:
\"
Etiam vel neque id nunc lacinia tincidunt.
\"
}"
,
"attempt_number"
:
1
,
"submitted_at"
:
"2014-04-30T21:05:29.372Z"
}
...
...
@@ -854,7 +854,7 @@
"uuid"
:
"cf5190b8-d0aa-11e3-a734-14109fd8dc43"
,
"student_item"
:
1
,
"created_at"
:
"2014-04-30T21:02:59.241Z"
,
"
raw_
answer"
:
"{
\"
text
\"
:
\"
Lorem ipsum dolor sit amet
\"
}"
,
"answer"
:
"{
\"
text
\"
:
\"
Lorem ipsum dolor sit amet
\"
}"
,
"attempt_number"
:
1
,
"submitted_at"
:
"2014-04-30T21:02:59.234Z"
}
...
...
openassessment/tests/data/db_fixtures/submitted.json
View file @
aa908378
...
...
@@ -477,9 +477,9 @@
"uuid"
:
"cf5190b8-d0aa-11e3-a734-14109fd8dc43"
,
"student_item"
:
1
,
"created_at"
:
"2014-04-30T21:02:59.241Z"
,
"
raw_
answer"
:
"{
\"
text
\"
:
\"
Lorem ipsum dolor sit amet
\"
}"
,
"answer"
:
"{
\"
text
\"
:
\"
Lorem ipsum dolor sit amet
\"
}"
,
"attempt_number"
:
1
,
"submitted_at"
:
"2014-04-30T21:02:59.234Z"
}
}
]
\ No newline at end of file
]
openassessment/tests/data/db_fixtures/unicode.json
View file @
aa908378
...
...
@@ -463,7 +463,7 @@
"uuid"
:
"99765973-d1f9-11e3-841a-14109fd8dc43"
,
"student_item"
:
2
,
"created_at"
:
"2014-05-02T12:59:30.290Z"
,
"
raw_
answer"
:
"{
\"
text
\"
:
\"\\
u0547
\\
ufec9
\\
u0e23
\\
u0547
\\
u0e23
\\
u0547
\\
u027c
\\
u0671
\\
u0e01
\\
ufeed
\"
}"
,
"answer"
:
"{
\"
text
\"
:
\"\\
u0547
\\
ufec9
\\
u0e23
\\
u0547
\\
u0e23
\\
u0547
\\
u027c
\\
u0671
\\
u0e01
\\
ufeed
\"
}"
,
"attempt_number"
:
1
,
"submitted_at"
:
"2014-05-02T12:59:30.283Z"
}
...
...
@@ -475,7 +475,7 @@
"uuid"
:
"8c52cfdc-d1f9-11e3-953c-14109fd8dc43"
,
"student_item"
:
1
,
"created_at"
:
"2014-05-02T12:59:08.243Z"
,
"
raw_
answer"
:
"{
\"
text
\"
:
\"\\
u0442
\\
u0454
\\
u0455
\\
u0442
\\
u0455
\\
u0442
\\
u044f
\\
u03b9
\\
u03b7
\\
ufeed
\"
}"
,
"answer"
:
"{
\"
text
\"
:
\"\\
u0442
\\
u0454
\\
u0455
\\
u0442
\\
u0455
\\
u0442
\\
u044f
\\
u03b9
\\
u03b7
\\
ufeed
\"
}"
,
"attempt_number"
:
1
,
"submitted_at"
:
"2014-05-02T12:59:08.234Z"
}
...
...
openassessment/workflow/serializers.py
View file @
aa908378
"""
Serializers are created to ensure models do not have to be accessed outside the
scope of the
Tim
APIs.
scope of the
ORA2
APIs.
"""
from
rest_framework
import
serializers
from
openassessment.workflow.models
import
AssessmentWorkflow
,
AssessmentWorkflowCancellation
class
AssessmentWorkflowSerializer
(
serializers
.
ModelSerializer
):
score
=
serializers
.
Field
(
source
=
'score'
)
score
=
serializers
.
ReadOnlyField
(
required
=
False
)
class
Meta
:
model
=
AssessmentWorkflow
...
...
@@ -22,20 +22,6 @@ class AssessmentWorkflowSerializer(serializers.ModelSerializer):
'score'
)
# Not implemented yet:
#
# class AssessmentWorkflowHistorySerializer(serializers.ModelSerializer):
# class Meta:
# model = AssessmentWorkflowHistory
# fields = (
# 'workflow',
# 'app',
# 'event_type',
# 'event_data',
# 'description',
# 'created_at'
# )
class
AssessmentWorkflowCancellationSerializer
(
serializers
.
ModelSerializer
):
"""
...
...
openassessment/xblock/data_conversion.py
View file @
aa908378
...
...
@@ -182,7 +182,7 @@ def prepare_submission_for_serialization(submission_data):
def
create_submission_dict
(
submission
,
prompts
):
"""
1. Convert from legacy format.
3
. Add prompts to submission['answer']['parts'] to simplify iteration in the template.
2
. Add prompts to submission['answer']['parts'] to simplify iteration in the template.
Args:
submission (dict): Submission dictionary.
...
...
@@ -191,7 +191,7 @@ def create_submission_dict(submission, prompts):
Returns:
dict
"""
parts
=
[{
'prompt'
:
prompt
,
'text'
:
''
}
for
prompt
in
prompts
]
parts
=
[{
'prompt'
:
prompt
,
'text'
:
''
}
for
prompt
in
prompts
]
if
'text'
in
submission
[
'answer'
]:
parts
[
0
][
'text'
]
=
submission
[
'answer'
]
.
pop
(
'text'
)
...
...
openassessment/xblock/test/test_validation.py
View file @
aa908378
...
...
@@ -86,7 +86,11 @@ class RubricValidationTest(TestCase):
is_released
=
data
.
get
(
'is_released'
,
False
)
is_example_based
=
data
.
get
(
'is_example_based'
,
False
)
success
,
msg
=
validate_rubric
(
data
[
'rubric'
],
current_rubric
,
is_released
,
is_example_based
,
STUB_I18N
data
[
'rubric'
],
current_rubric
,
is_released
,
is_example_based
,
STUB_I18N
)
self
.
assertTrue
(
success
)
self
.
assertEqual
(
msg
,
u''
)
...
...
requirements/base.txt
View file @
aa908378
...
...
@@ -6,7 +6,7 @@
git+https://github.com/edx/XBlock.git@9c634481dfc85a17dcb3351ca232d7098a38e10e#egg=XBlock
# edx-submissions
git+https://github.com/edx/edx-submissions.git@
9538ee8a971d04dc1cb05e88f6aa0c36b224455c#egg=edx-submissions
git+https://github.com/edx/edx-submissions.git@
14aeaa9e30f9a408b34ffaf6d78409dedaad015a#egg=edx-submissions==0.1.0
# Third Party Requirements
boto>=2.32.1,<3.0.0
...
...
@@ -16,8 +16,9 @@ django>=1.4,<1.5
django-celery==3.1.16
django-extensions==1.5.5
django-model-utils==2.3.1
djangorestframework
<2.4
djangorestframework
>=3.1,<3.2
dogapi==1.2.1
jsonfield==1.0.3
lazy==1.1
loremipsum==1.0.5
python-dateutil==2.1
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment