diff --git a/course/__init__.py b/course/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1dab58eb27c37a210fe8f076e8ff8680cf9ade32 100644 --- a/course/__init__.py +++ b/course/__init__.py @@ -0,0 +1 @@ +default_app_config = 'course.apps.CourseConfig' \ No newline at end of file diff --git a/course/apps.py b/course/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..233c608a914bf4d2262c62589a12b36ab1c19197 --- /dev/null +++ b/course/apps.py @@ -0,0 +1,8 @@ +from django.apps import AppConfig +from django.utils.translation import ugettext_lazy as _ + + +class CourseConfig(AppConfig): + name = 'course' + # for translation of the name of "Course" app displayed in admin. + verbose_name = _("Course module") \ No newline at end of file diff --git a/course/auth.py b/course/auth.py index a9a3872fe8d048b889519a41f77a3b11f1516518..18d7715749775907530ffb57600d750c015e3cb1 100644 --- a/course/auth.py +++ b/course/auth.py @@ -124,7 +124,8 @@ class ImpersonateForm(StyledForm): key=lambda user: user.last_name.lower()) ], required=True, - help_text="Select user to impersonate.") + help_text="Select user to impersonate.", + label="User") self.helper.add_input(Submit("submit", "Impersonate", css_class="col-lg-offset-2")) @@ -269,7 +270,7 @@ def sign_in_by_user_pw(request): class SignUpForm(StyledModelForm): - username = forms.CharField(required=True, max_length=30) + username = forms.CharField(required=True, max_length=30, label="Username") class Meta: model = User @@ -352,7 +353,7 @@ def sign_up(request): class ResetPasswordForm(StyledForm): - email = forms.EmailField(required=True) + email = forms.EmailField(required=True, label="Email") def __init__(self, *args, **kwargs): super(ResetPasswordForm, self).__init__(*args, **kwargs) @@ -412,8 +413,10 @@ def reset_password(request): class ResetPasswordStage2Form(StyledForm): - password = forms.CharField(widget=forms.PasswordInput()) - password_repeat = forms.CharField(widget=forms.PasswordInput()) + password = forms.CharField(widget=forms.PasswordInput(), + label="Password") + password_repeat = forms.CharField(widget=forms.PasswordInput(), + label="Password repeat") def __init__(self, *args, **kwargs): super(ResetPasswordStage2Form, self).__init__(*args, **kwargs) @@ -484,7 +487,7 @@ def reset_password_stage2(request, user_id, sign_in_key): # {{{ email sign-in flow class SignInByEmailForm(StyledForm): - email = forms.EmailField(required=True) + email = forms.EmailField(required=True, label="Email") def __init__(self, *args, **kwargs): super(SignInByEmailForm, self).__init__(*args, **kwargs) diff --git a/course/calendar.py b/course/calendar.py index c271e0890ed73912c6b78ae7d37a071e2ab0e183..01511d2c41476c64656dd51319fbf60f2bb84807 100644 --- a/course/calendar.py +++ b/course/calendar.py @@ -24,7 +24,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ - +from django.utils.translation import ugettext_lazy as _, pgettext_lazy from django.contrib.auth.decorators import login_required from course.utils import course_view, render_course_page from django.core.exceptions import PermissionDenied, ObjectDoesNotExist @@ -72,17 +72,23 @@ def check_events(pctx): class RecurringEventForm(StyledForm): kind = forms.CharField(required=True, - help_text="Should be lower_case_with_underscores, no spaces allowed.") + help_text="Should be lower_case_with_underscores, no spaces allowed.", + label=pgettext_lazy("kind of event","Kind of event")) time = forms.DateTimeField( widget=DateTimePicker( - options={"format": "YYYY-MM-DD HH:mm", "sideBySide": True})) - duration_in_minutes = forms.FloatField(required=False) + options={"format": "YYYY-MM-DD HH:mm", "sideBySide": True}), + label=pgettext_lazy("starting time of event","Starting time")) + duration_in_minutes = forms.FloatField(required=False, + label="Duration in minutes") interval = forms.ChoiceField(required=True, choices=( ("weekly", "Weekly"), - )) - starting_ordinal = forms.IntegerField(required=False) - count = forms.IntegerField(required=True) + ), + label=pgettext_lazy("interval of recurring events","Interval")) + starting_ordinal = forms.IntegerField(required=False, + label=pgettext_lazy("Starting ordinal of recurring events","Starting ordinal")) + count = forms.IntegerField(required=True, + label=pgettext_lazy("Count of recurring events","Count")) def __init__(self, *args, **kwargs): super(RecurringEventForm, self).__init__(*args, **kwargs) @@ -190,8 +196,10 @@ def create_recurring_events(pctx): class RenumberEventsForm(StyledForm): kind = forms.CharField(required=True, - help_text="Should be lower_case_with_underscores, no spaces allowed.") - starting_ordinal = forms.IntegerField(required=True, initial=1) + help_text="Should be lower_case_with_underscores, no spaces allowed.", + label=pgettext_lazy("kind of event","Kind of event")) + starting_ordinal = forms.IntegerField(required=True, initial=1, + label=pgettext_lazy("Starting ordinal of recurring events","Starting ordinal")) def __init__(self, *args, **kwargs): super(RenumberEventsForm, self).__init__(*args, **kwargs) diff --git a/course/enrollment.py b/course/enrollment.py index 71ec14db285d5ce370d025461b87b9f9b805c42d..ba6846239bdb78c52ce0187bfc5de0b6cd7f371c 100644 --- a/course/enrollment.py +++ b/course/enrollment.py @@ -207,9 +207,11 @@ deny_enrollment.short_description = "Deny enrollment" class BulkPreapprovalsForm(StyledForm): role = forms.ChoiceField( choices=PARTICIPATION_ROLE_CHOICES, - initial=participation_role.student) + initial=participation_role.student, + label="Role") emails = forms.CharField(required=True, widget=forms.Textarea, - help_text="Enter fully qualified email addresses, one per line.") + help_text="Enter fully qualified email addresses, one per line.", + label="Emails") def __init__(self, *args, **kwargs): super(BulkPreapprovalsForm, self).__init__(*args, **kwargs) diff --git a/course/flow.py b/course/flow.py index 1491530aaaf3d46f787a215903374be70539edc3..187de82b2a950534186737cce5a62bc4c6f76831 100644 --- a/course/flow.py +++ b/course/flow.py @@ -1372,17 +1372,20 @@ class RegradeFlowForm(StyledForm): self.fields["flow_id"] = forms.ChoiceField( choices=[(fid, fid) for fid in flow_ids], initial=participation_role.student, - required=True) + required=True, + label="Flow ID") self.fields["access_rules_tag"] = forms.CharField( required=False, help_text="If non-empty, limit the regrading to sessions started " - "under this access rules tag.") + "under this access rules tag.", + label="Access rules tag") self.fields["regraded_session_in_progress"] = forms.ChoiceField( choices=( ("any", "Regrade in-progress and not-in-progress sessions"), ("yes", "Regrade in-progress sessions only"), ("no", "Regrade not-in-progress sessions only"), - )) + ), + label="Regraded session in progress") self.helper.add_input( Submit("regrade", "Regrade", css_class="col-lg-offset-2")) diff --git a/course/grades.py b/course/grades.py index 6629115bf5d4cdbfbcd54d9be80427d747efa1a6..965b6587b272f7b1a767f5684450c2c071633c4e 100644 --- a/course/grades.py +++ b/course/grades.py @@ -336,12 +336,14 @@ class ModifySessionsForm(StyledForm): self.fields["rule_tag"] = forms.ChoiceField( choices=tuple( (rule_tag, str(rule_tag)) - for rule_tag in session_rule_tags)) + for rule_tag in session_rule_tags), + label="Rule tag") self.fields["past_due_only"] = forms.BooleanField( required=False, initial=True, help_text="Only act on in-progress sessions that are past " - "their access rule's due date (applies to 'expire' and 'end')") + "their access rule's due date (applies to 'expire' and 'end')", + label="Past due only") self.helper.add_input( Submit("expire", "Expire sessions", @@ -644,10 +646,12 @@ class ReopenSessionForm(StyledForm): [(tag, tag) for tag in tags], initial=(current_tag if current_tag is not None - else NONE_SESSION_TAG)) + else NONE_SESSION_TAG), + label="Set access rules tag") self.fields["comment"] = forms.CharField( - widget=forms.Textarea, required=True) + widget=forms.Textarea, required=True, + label="Comment") self.helper.add_input( Submit( @@ -932,31 +936,39 @@ class ImportGradesForm(StyledForm): .order_by("identifier")), help_text="Click to create " "a new grading opportunity. Reload this form when done." - % reverse("admin:course_gradingopportunity_add")) + % reverse("admin:course_gradingopportunity_add"), + label="Grading opportunity") self.fields["attempt_id"] = forms.CharField( initial="main", - required=True) - self.fields["file"] = forms.FileField() + required=True, + label="Attempt ID") + self.fields["file"] = forms.FileField( + label="File") self.fields["format"] = forms.ChoiceField( choices=( ("csvhead", "CSV with Header"), ("csv", "CSV"), - )) + ), + label="Format") self.fields["id_column"] = forms.IntegerField( help_text="1-based column index for the Email or NetID " "used to locate student record", - min_value=1) + min_value=1, + label="User ID column") self.fields["points_column"] = forms.IntegerField( help_text="1-based column index for the (numerical) grade", - min_value=1) + min_value=1, + label="Points column") self.fields["feedback_column"] = forms.IntegerField( help_text="1-based column index for further (textual) feedback", - min_value=1, required=False) + min_value=1, required=False, + label="Feedback column") self.fields["max_points"] = forms.DecimalField( - initial=100) + initial=100, + label="Max points") self.helper.add_input( Submit("preview", "Preview", diff --git a/course/im.py b/course/im.py index e9ba5ce42d5edd03948192fd8d9018c49de86c6a..730899e2383950a80128f21293f9a5e66ff1b6d5 100644 --- a/course/im.py +++ b/course/im.py @@ -47,7 +47,8 @@ import threading # {{{ instant message class InstantMessageForm(forms.Form): - message = forms.CharField(required=True, max_length=200) + message = forms.CharField(required=True, max_length=200, + label="message") def __init__(self, *args, **kwargs): self.helper = FormHelper() @@ -163,10 +164,10 @@ def send_instant_message(pctx): xmpp = get_xmpp_connection(pctx.course) if xmpp.is_recipient_online(): - form_text = "Recipient is Online." + form_text = "Recipient is Online." else: - form_text = "Recipient is Offline." - form_text = "
%s
" % form_text + form_text = "Recipient is Offline." + form_text = "
%s
" % form_text if request.method == "POST": form = InstantMessageForm(request.POST, request.FILES) diff --git a/course/models.py b/course/models.py index 19f38beb7ae55102fe13874ed4508196770e0ebe..7075adc9eff83227442e790d6a960a709a8549ab 100644 --- a/course/models.py +++ b/course/models.py @@ -29,6 +29,13 @@ from django.contrib.auth.models import User from django.utils.timezone import now from django.core.urlresolvers import reverse from django.core.exceptions import ValidationError, ObjectDoesNotExist +from django.utils.translation import ( + ugettext_lazy as _ , + pgettext_lazy, + ugettext_noop, + string_concat, + ugettext, + ) from course.constants import ( # noqa user_status, USER_STATUS_CHOICES, @@ -53,11 +60,14 @@ class Facility(models.Model): identifier = models.CharField(max_length=50, unique=True, help_text="Format is lower-case-with-hyphens. " - "Do not use spaces.") - description = models.CharField(max_length=100) + "Do not use spaces.", + verbose_name = "Facility ID") + description = models.CharField(max_length=100, + verbose_name = "Facility description") class Meta: - verbose_name_plural = "facilities" + verbose_name = "Facility" + verbose_name_plural = "Facilities" def __unicode__(self): return self.identifier @@ -70,12 +80,13 @@ class FacilityIPRange(models.Model): ip_range = models.CharField( max_length=200, - verbose_name="IP Range") + verbose_name="IP range") - description = models.CharField(max_length=100,) + description = models.CharField(max_length=100, + verbose_name = 'Ip range description') class Meta: - verbose_name = "Facility IP Range" + verbose_name = "Facility IP range" def clean(self): import ipaddr @@ -102,12 +113,18 @@ def get_user_status(user): class UserStatus(models.Model): - user = models.OneToOneField(User, db_index=True, related_name="user_status") + user = models.OneToOneField(User, db_index=True, related_name="user_status", + verbose_name = 'User ID') status = models.CharField(max_length=50, - choices=USER_STATUS_CHOICES) + choices=USER_STATUS_CHOICES, + verbose_name = 'User status') sign_in_key = models.CharField(max_length=50, - null=True, unique=True, db_index=True, blank=True) - key_time = models.DateTimeField(default=now) + help_text="NEED HELP TEXT", + null=True, unique=True, db_index=True, blank=True, + verbose_name = 'Sign in key') + key_time = models.DateTimeField(default=now, + help_text="NEED HELP TEXT", + verbose_name = 'Key time') editor_mode = models.CharField(max_length=20, choices=( @@ -116,14 +133,17 @@ class UserStatus(models.Model): ("emacs", "Emacs"), ("vim", "Vim"), ), - default="default") + default="default", + # Translators: the text editor used by participants + verbose_name = "Editor mode") class Meta: - verbose_name_plural = "user statuses" + verbose_name = "User status" + verbose_name_plural = "User statuses" ordering = ("key_time",) def __unicode__(self): - return "User status for %s" % self.user + return "User status for %(user)s" % {'user':self.user} # }}} @@ -135,77 +155,98 @@ class Course(models.Model): help_text="A course identifier. Alphanumeric with dashes, " "no spaces. This is visible in URLs and determines the location " "on your file system where the course's git repository lives.", + verbose_name = 'Course ID', db_index=True) hidden = models.BooleanField( default=True, - help_text="Is the course only accessible to course staff?") + help_text="Is the course only accessible to course staff?", + verbose_name = 'Hidden to student') listed = models.BooleanField( default=True, - help_text="Should the course be listed on the main page?") + help_text="Should the course be listed on the main page?", + verbose_name = 'Listed on main page') accepts_enrollment = models.BooleanField( - default=True) + default=True, + verbose_name = 'Accepts enrollment') valid = models.BooleanField( default=True, - help_text="Whether the course content has passed validation.") + help_text="Whether the course content has passed validation.", + verbose_name = 'Valid') git_source = models.CharField(max_length=200, blank=True, help_text="A Git URL from which to pull course updates. " "If you're just starting out, enter " "git://github.com/inducer/relate-sample " - "to get some sample content.") + "to get some sample content.", + verbose_name = 'git source') ssh_private_key = models.TextField(blank=True, help_text="An SSH private key to use for Git authentication. " - "Not needed for the sample URL above.") + "Not needed for the sample URL above.", + verbose_name = 'SSH private key') course_file = models.CharField(max_length=200, default="course.yml", help_text="Name of a YAML file in the git repository that contains " - "the root course descriptor.") + "the root course descriptor.", + verbose_name = 'Course file') events_file = models.CharField(max_length=200, default="events.yml", help_text="Name of a YAML file in the git repository that contains " - "calendar information.") + "calendar information.", + verbose_name = 'Events file') enrollment_approval_required = models.BooleanField( default=False, help_text="If set, each enrolling student must be " - "individually approved.") + "individually approved.", + verbose_name = 'Enrollment approval required') enrollment_required_email_suffix = models.CharField( max_length=200, blank=True, null=True, help_text="Enrollee's email addresses must end in the " - "specified suffix, such as '@illinois.edu'.") + "specified suffix, such as '@illinois.edu'.", + verbose_name = 'Enrollment required email suffix') from_email = models.EmailField( help_text="This email address will be used in the 'From' line " - "of automated emails sent by RELATE.") + "of automated emails sent by RELATE."), + verbose_name = 'From email') notify_email = models.EmailField( help_text="This email address will receive " - "notifications about the course.") + "notifications about the course.", + verbose_name = 'Notify email') # {{{ XMPP course_xmpp_id = models.CharField(max_length=200, blank=True, null=True, help_text="(Required only if the instant message feature is desired.) " "The Jabber/XMPP ID (JID) the course will use to sign in to an " - "XMPP server.") + "XMPP server.", + verbose_name = 'Course xmpp ID') course_xmpp_password = models.CharField(max_length=200, blank=True, null=True, help_text="(Required only if the instant message feature is desired.) " - "The password to go with the JID above.") + "The password to go with the JID above.", + verbose_name = 'Course xmpp password') recipient_xmpp_id = models.CharField(max_length=200, blank=True, null=True, help_text="(Required only if the instant message feature is desired.) " - "The JID to which instant messages will be sent.") + "The JID to which instant messages will be sent.", + verbose_name = 'Recipient xmpp ID') # }}} active_git_commit_sha = models.CharField(max_length=200, null=False, - blank=False) + blank=False, + verbose_name = 'Active git commit sha') participants = models.ManyToManyField(User, through='Participation') + class Meta: + verbose_name = "Course" + verbose_name_plural = "Courses" + def __unicode__(self): return self.identifier @@ -222,21 +263,29 @@ class Event(models.Model): course content. """ - course = models.ForeignKey(Course) + course = models.ForeignKey(Course, + verbose_name = 'Course ID') kind = models.CharField(max_length=50, - help_text="Should be lower_case_with_underscores, no spaces allowed.") - ordinal = models.IntegerField(blank=True, null=True) - - time = models.DateTimeField() - end_time = models.DateTimeField(null=True, blank=True) - + help_text="Should be lower_case_with_underscores, no spaces allowed.", + verbose_name = 'Kind of event') + ordinal = models.IntegerField(blank=True, null=True, + verbose_name = 'Ordinal of event') + + time = models.DateTimeField(verbose_name = 'Start time') + end_time = models.DateTimeField(null=True, blank=True, + verbose_name = 'End time') all_day = models.BooleanField(default=False, + # Translators: for when the due time is "All day", how the webpage of a event is displayed. help_text="Only affects the rendering in the class calendar, " - "in that a start time is not shown") + "in that a start time is not shown", + verbose_name = 'All day') - shown_in_calendar = models.BooleanField(default=True) + shown_in_calendar = models.BooleanField(default=True, + verbose_name = 'Shown in calendar') class Meta: + verbose_name = "Event" + verbose_name_plural = "Events" ordering = ("course", "time") unique_together = (("course", "kind", "ordinal")) @@ -252,72 +301,97 @@ class Event(models.Model): # {{{ participation class ParticipationTag(models.Model): - course = models.ForeignKey(Course) - + course = models.ForeignKey(Course, + verbose_name = 'Course ID') name = models.CharField(max_length=100, unique=True, help_text="Format is lower-case-with-hyphens. " - "Do not use spaces.") + "Do not use spaces.", + verbose_name = 'Name of ParticipationTag') def clean(self): import re name_valid_re = re.compile(r"^\w+$") if name_valid_re.match(self.name) is None: + # Translators: "Name" is the name of a ParticipationTag raise ValidationError({"name": "Name contains invalid characters."}) def __unicode__(self): return "%s (%s)" % (self.name, self.course) class Meta: + verbose_name = "Participation tag" + verbose_name_plural = "Participation tags" unique_together = (("course", "name"),) ordering = ("course", "name") class Participation(models.Model): - user = models.ForeignKey(User) - course = models.ForeignKey(Course, related_name="participations") + user = models.ForeignKey(User, + verbose_name = 'User ID') + course = models.ForeignKey(Course, related_name="participations", + verbose_name = 'Course ID') - enroll_time = models.DateTimeField(default=now) + enroll_time = models.DateTimeField(default=now, + verbose_name = 'Enroll time') role = models.CharField(max_length=50, choices=PARTICIPATION_ROLE_CHOICES, help_text="Instructors may update course content. " "Teaching assistants may access and change grade data. " "Observers may access analytics. " - "Each role includes privileges from subsequent roles.") + "Each role includes privileges from subsequent roles.", + verbose_name = 'Participation role') status = models.CharField(max_length=50, - choices=PARTICIPATION_STATUS_CHOICES) + choices=PARTICIPATION_STATUS_CHOICES, + verbose_name = 'Participation status') time_factor = models.DecimalField( max_digits=10, decimal_places=2, - default=1) + default=1, + help_text="NEED HELP TEXT", + verbose_name = 'Time factor') preview_git_commit_sha = models.CharField(max_length=200, null=True, - blank=True) + blank=True, + verbose_name = 'Preview git commit sha') - tags = models.ManyToManyField(ParticipationTag, blank=True) + tags = models.ManyToManyField(ParticipationTag, blank=True, + help_text="NEED HELP TEXT", + verbose_name = 'Tags') def __unicode__(self): - return "%s in %s as %s" % ( - self.user, self.course, self.role) + # Translators: displayed format of Participation: some user in some course as some role + return "%(user)s in %(course)s as %(role)s" % { + 'user':self.user, 'course':self.course, 'role':dict(PARTICIPATION_ROLE_CHOICES).get(self.role).lower()} class Meta: + verbose_name = "Participation" + verbose_name_plural = "Participations" unique_together = (("user", "course"),) ordering = ("course", "user") class ParticipationPreapproval(models.Model): - email = models.EmailField(max_length=254) - course = models.ForeignKey(Course) + email = models.EmailField(max_length=254, + verbose_name = 'Email') + course = models.ForeignKey(Course, + verbose_name = 'Course ID') role = models.CharField(max_length=50, - choices=PARTICIPATION_ROLE_CHOICES) + choices=PARTICIPATION_ROLE_CHOICES, + verbose_name = 'Role') - creator = models.ForeignKey(User, null=True) - creation_time = models.DateTimeField(default=now, db_index=True) + creator = models.ForeignKey(User, null=True, + verbose_name = 'Creator') + creation_time = models.DateTimeField(default=now, db_index=True, + verbose_name = 'Creation time') def __unicode__(self): - return "%s in %s" % (self.email, self.course) + # Translators: somebody's email in some course in Particiaption Preapproval + return "%(email)s in %(course)s" % {"email": self.email, "course": self.course} class Meta: + verbose_name = "Participation preapproval" + verbose_name_plural = "Participation preapprovals" unique_together = (("course", "email"),) ordering = ("course", "email") @@ -325,34 +399,52 @@ class ParticipationPreapproval(models.Model): class InstantFlowRequest(models.Model): - course = models.ForeignKey(Course) - flow_id = models.CharField(max_length=200) - start_time = models.DateTimeField(default=now) - end_time = models.DateTimeField() - cancelled = models.BooleanField(default=False) - + course = models.ForeignKey(Course, + verbose_name = 'Course ID') + flow_id = models.CharField(max_length=200, + verbose_name = 'Flow ID') + start_time = models.DateTimeField(default=now, + verbose_name = 'Start time') + end_time = models.DateTimeField( + verbose_name = 'End time') + cancelled = models.BooleanField(default=False, + verbose_name = 'Cancelled') + + class Meta: + verbose_name = "Instant flow request" + verbose_name_plural = "Instant flow requests" # {{{ flow session class FlowSession(models.Model): # This looks like it's redundant with 'participation', below--but it's not. # 'participation' is nullable. - course = models.ForeignKey(Course) + course = models.ForeignKey(Course, + verbose_name = 'Course ID') participation = models.ForeignKey(Participation, null=True, blank=True, - db_index=True) - active_git_commit_sha = models.CharField(max_length=200) - flow_id = models.CharField(max_length=200, db_index=True) - start_time = models.DateTimeField(default=now) - completion_time = models.DateTimeField(null=True, blank=True) - page_count = models.IntegerField(null=True, blank=True) - - in_progress = models.BooleanField(default=None) + db_index=True, + verbose_name = 'Participation') + active_git_commit_sha = models.CharField(max_length=200, + verbose_name = 'Active git commit sha') + flow_id = models.CharField(max_length=200, db_index=True, + verbose_name = 'Flow ID') + start_time = models.DateTimeField(default=now, + verbose_name = 'Start time') + completion_time = models.DateTimeField(null=True, blank=True, + verbose_name = 'Completition time') + page_count = models.IntegerField(null=True, blank=True, + verbose_name = 'Page count') + + in_progress = models.BooleanField(default=None, + verbose_name = 'In progress') access_rules_tag = models.CharField(max_length=200, null=True, - blank=True) + blank=True, + verbose_name = 'Access rules tag') expiration_mode = models.CharField(max_length=20, null=True, default=flow_session_expiration_mode.end, - choices=FLOW_SESSION_EXPIRATION_MODE_CHOICES) + choices=FLOW_SESSION_EXPIRATION_MODE_CHOICES, + verbose_name = 'Expiration mode') # Non-normal: These fields can be recomputed, albeit at great expense. # @@ -360,24 +452,29 @@ class FlowSession(models.Model): # some flow sessions are not for credit and still have results. points = models.DecimalField(max_digits=10, decimal_places=2, - blank=True, null=True) + blank=True, null=True, + verbose_name = 'Points') max_points = models.DecimalField(max_digits=10, decimal_places=2, - blank=True, null=True) - result_comment = models.TextField(blank=True, null=True) + blank=True, null=True, + verbose_name = 'Max point') + result_comment = models.TextField(blank=True, null=True, + verbose_name = 'Result comment') class Meta: + verbose_name = "Flow session" + verbose_name_plural = "Flow sessions" ordering = ("course", "-start_time") def __unicode__(self): if self.participation is None: - return "anonymous session %d on '%s'" % ( - self.id, - self.flow_id) + return "anonymous session %(session_id)d on '%(flow_id)s'" % { + 'session_id':self.id, + 'flow_id':self.flow_id} else: - return "%s's session %d on '%s'" % ( - self.participation.user, - self.id, - self.flow_id) + return "%(user)s's session %(session_id)d on '%(flow_id)s'" % { + 'user':self.participation.user, + 'session_id':self.id, + 'flow_id':self.flow_id} def append_comment(self, s): if s is None: @@ -419,23 +516,32 @@ class FlowSession(models.Model): # {{{ flow page data class FlowPageData(models.Model): - flow_session = models.ForeignKey(FlowSession, related_name="page_data") - ordinal = models.IntegerField(null=True, blank=True) + flow_session = models.ForeignKey(FlowSession, related_name="page_data", + verbose_name = 'Flow session') + ordinal = models.IntegerField(null=True, blank=True, + verbose_name = 'Ordinal') - group_id = models.CharField(max_length=200) - page_id = models.CharField(max_length=200) + group_id = models.CharField(max_length=200, + verbose_name = 'Group ID') + page_id = models.CharField(max_length=200, + verbose_name = 'Page ID') - data = JSONField(null=True, blank=True) + data = JSONField(null=True, blank=True, + # Show correct characters in admin for non ascii languages. + dump_kwargs={'ensure_ascii': False}, + verbose_name = 'Data') class Meta: - verbose_name_plural = "flow page data" + verbose_name = "Flow page data" + verbose_name_plural = "Flow page data" def __unicode__(self): - return "Data for page '%s/%s' (ordinal %s) in %s" % ( - self.group_id, - self.page_id, - self.ordinal, - self.flow_session) + # flow page data + return "Data for page '%(group_id)s/%(page_id)s' (ordinal %(ordinal)s) in %(flow_session)s" % { + 'group_id':self.group_id, + 'page_id':self.page_id, + 'ordinal':self.ordinal, + 'flow_session':self.flow_session} # Django's templates are a little daft. No arithmetic--really? def previous_ordinal(self): @@ -453,15 +559,25 @@ class FlowPageVisit(models.Model): # This is redundant (because the FlowSession is available through # page_data), but it helps the admin site understand the link # and provide editing. - flow_session = models.ForeignKey(FlowSession, db_index=True) - - page_data = models.ForeignKey(FlowPageData, db_index=True) - visit_time = models.DateTimeField(default=now, db_index=True) - remote_address = models.GenericIPAddressField(null=True, blank=True) - - is_synthetic = models.BooleanField(default=False) - - answer = JSONField(null=True, blank=True) + flow_session = models.ForeignKey(FlowSession, db_index=True, + verbose_name = 'Flow session') + + page_data = models.ForeignKey(FlowPageData, db_index=True, + verbose_name = 'Page data') + visit_time = models.DateTimeField(default=now, db_index=True, + verbose_name = 'Visit time') + remote_address = models.GenericIPAddressField(null=True, blank=True, + verbose_name = 'Remote address') + + is_synthetic = models.BooleanField(default=False, + help_text="NEED HELP TEXT" + verbose_name = 'Is synthetic') + + answer = JSONField(null=True, blank=True, + # Show correct characters in admin for non ascii languages. + dump_kwargs={'ensure_ascii': False}, + # Translators: "Answer" is a Noun. + verbose_name = 'Answer') # is_submitted_answer may seem redundant with answers being # non-NULL, but it isn't. This supports saved (but as @@ -471,21 +587,27 @@ class FlowPageVisit(models.Model): # (Should coincide with 'answer is None') # True means it's a final, submitted answer fit for grading. # False means it's just a saved answer. - is_submitted_answer = models.NullBooleanField() + is_submitted_answer = models.NullBooleanField( + # Translators: determine whether the answer is a final, submitted answer fit for grading. + verbose_name = 'Is submitted answer') def __unicode__(self): - result = "'%s/%s' in '%s' on %s" % ( - self.page_data.group_id, - self.page_data.page_id, - self.flow_session, - self.visit_time) + # Translators: flow page visit + result = "'%(group_id)s/%(page_id)s' in '%(session)s' on %(time)s" % { + 'group_id':self.page_data.group_id, + 'page_id':self.page_data.page_id, + 'session':self.flow_session, + 'time':self.visit_time} if self.answer is not None: - result += " (with answer)" + # Translators: flow page visit: if the answer is provided by user then append the string. + result += unicode(" (with answer)") return result class Meta: + verbose_name = "Flow page visit" + verbose_name_plural = "Flow page visits" # These must be distinguishable, to figure out what came later. unique_together = (("page_data", "visit_time"),) @@ -511,34 +633,49 @@ class FlowPageVisit(models.Model): # {{{ flow page visit grade class FlowPageVisitGrade(models.Model): - visit = models.ForeignKey(FlowPageVisit, related_name="grades") + visit = models.ForeignKey(FlowPageVisit, related_name="grades", + verbose_name = 'Visit') # NULL means 'autograded' - grader = models.ForeignKey(User, null=True, blank=True) - grade_time = models.DateTimeField(db_index=True, default=now) + grader = models.ForeignKey(User, null=True, blank=True, + verbose_name = 'Grader') + grade_time = models.DateTimeField(db_index=True, default=now, + verbose_name = 'Grade time') graded_at_git_commit_sha = models.CharField( - max_length=200, null=True, blank=True) + max_length=200, null=True, blank=True, + verbose_name = 'Graded at git commit sha') - grade_data = JSONField(null=True, blank=True) + grade_data = JSONField(null=True, blank=True, + # Show correct characters in admin for non ascii languages. + dump_kwargs={'ensure_ascii': False}, + verbose_name = 'Grade data') # This data should be recomputable, but we'll cache it here, # because it might be very expensive (container-launch expensive # for code questions, for example) to recompute. max_points = models.FloatField(null=True, blank=True, + # Translators: max point in grade help_text="Point value of this question when receiving " - "full credit.") + "full credit.", + verbose_name = 'Max points') correctness = models.FloatField(null=True, blank=True, + # Translators: correctness in grade help_text="Real number between zero and one (inclusively) " - "indicating the degree of correctness of the answer.") + "indicating the degree of correctness of the answer.", + verbose_name = 'Correctness') # This JSON object has fields corresponding to # :class:`course.page.AnswerFeedback`, except for # :attr:`course.page.AnswerFeedback.correctness`, which is stored # separately for efficiency. - feedback = JSONField(null=True, blank=True) + feedback = JSONField(null=True, blank=True, + # Show correct characters in admin for non ascii languages. + dump_kwargs={'ensure_ascii': False}, + # Translators: "Feedback" stands for the feedback of answers. + verbose_name = 'Feedback') def percentage(self): if self.correctness is not None: @@ -553,23 +690,31 @@ class FlowPageVisitGrade(models.Model): return None class Meta: + verbose_name = "Flow page visit grade" + verbose_name_plural = "Flow page visit grades" # These must be distinguishable, to figure out what came later. unique_together = (("visit", "grade_time"),) ordering = ("visit", "grade_time") def __unicode__(self): - return "grade of %s: %s" % ( - self.visit, self.percentage()) + # information on FlowPageVisitGrade class + return "grade of %(visit)s: %(percentage)s" % { + 'visit':self.visit, 'percentage':self.percentage()} class FlowPageBulkFeedback(models.Model): # We're only storing one of these per page, because # they're 'bulk' (i.e. big, like plots or program output) - page_data = models.OneToOneField(FlowPageData) - grade = models.ForeignKey(FlowPageVisitGrade) + page_data = models.OneToOneField(FlowPageData, + verbose_name = 'Page data') + grade = models.ForeignKey(FlowPageVisitGrade, + verbose_name = 'Grade') - bulk_feedback = JSONField(null=True, blank=True) + bulk_feedback = JSONField(null=True, blank=True, + # Show correct characters in admin for non ascii languages. + dump_kwargs={'ensure_ascii': False}, + verbose_name = 'Bulk feedback') def update_bulk_feedback(page_data, grade, bulk_feedback_json): @@ -626,44 +771,60 @@ def validate_stipulations(stip): class FlowAccessException(models.Model): # deprecated - participation = models.ForeignKey(Participation, db_index=True) - flow_id = models.CharField(max_length=200, blank=False, null=False) - expiration = models.DateTimeField(blank=True, null=True) + participation = models.ForeignKey(Participation, db_index=True, + verbose_name = 'Participation') + flow_id = models.CharField(max_length=200, blank=False, null=False, + verbose_name = 'Flow ID') + expiration = models.DateTimeField(blank=True, null=True, + verbose_name = 'Expiration') stipulations = JSONField(blank=True, null=True, + # Translators: help text for stipulations in FlowAccessException (deprecated) help_text="A dictionary of the same things that can be added " "to a flow access rule, such as allowed_session_count or " "credit_percent. If not specified here, values will default " "to the stipulations in the course content.", - validators=[validate_stipulations]) + validators=[validate_stipulations], + dump_kwargs={'ensure_ascii': False}, + verbose_name = 'Stipulations') - creator = models.ForeignKey(User, null=True) - creation_time = models.DateTimeField(default=now, db_index=True) + creator = models.ForeignKey(User, null=True, + verbose_name = 'Creator') + creation_time = models.DateTimeField(default=now, db_index=True, + verbose_name = 'Creation time') is_sticky = models.BooleanField( default=False, + # Translators: deprecated help_text="Check if a flow started under this " "exception rule set should stay " - "under this rule set until it is expired.") + "under this rule set until it is expired.", + # Translators: deprecated + verbose_name = 'Is sticky') - comment = models.TextField(blank=True, null=True) + comment = models.TextField(blank=True, null=True, + verbose_name = 'Comment') def __unicode__(self): - return "Access exception for '%s' to '%s' in '%s'" % ( - self.participation.user, self.flow_id, - self.participation.course) + # Translators: flow access exception in admin (deprecated) + return "Access exception for '%(user)s' to '%(flow_id)s' in '%(course)s'" % { + 'user':self.participation.user, 'flow_id':self.flow_id, + 'course':self.participation.course} class FlowAccessExceptionEntry(models.Model): # deprecated exception = models.ForeignKey(FlowAccessException, - related_name="entries") + related_name="entries", + verbose_name = 'Exception') permission = models.CharField(max_length=50, - choices=FLOW_PERMISSION_CHOICES) + choices=FLOW_PERMISSION_CHOICES, + verbose_name = 'Permission') class Meta: - verbose_name_plural = "flow access exception entries" + # Translators: FlowAccessExceptionEntry (deprecated) + verbose_name_plural = "Flow access exception entries" def __unicode__(self): return self.permission @@ -672,25 +833,35 @@ class FlowAccessExceptionEntry(models.Model): class FlowRuleException(models.Model): - flow_id = models.CharField(max_length=200, blank=False, null=False) - participation = models.ForeignKey(Participation, db_index=True) - expiration = models.DateTimeField(blank=True, null=True) + flow_id = models.CharField(max_length=200, blank=False, null=False, + verbose_name = 'Flow ID') + participation = models.ForeignKey(Participation, db_index=True, + verbose_name = 'Participation') + expiration = models.DateTimeField(blank=True, null=True, + verbose_name = 'Expiration') - creator = models.ForeignKey(User, null=True) - creation_time = models.DateTimeField(default=now, db_index=True) + creator = models.ForeignKey(User, null=True, + verbose_name = 'Creator') + creation_time = models.DateTimeField(default=now, db_index=True, + verbose_name = 'Creation time') - comment = models.TextField(blank=True, null=True) + comment = models.TextField(blank=True, null=True, + verbose_name = 'Comment') kind = models.CharField(max_length=50, blank=False, null=False, - choices=FLOW_RULE_KIND_CHOICES) - rule = YAMLField(blank=False, null=False) - active = models.BooleanField(default=True) + choices=FLOW_RULE_KIND_CHOICES, + verbose_name = 'Kind') + rule = YAMLField(blank=False, null=False, + verbose_name = 'Rule') + active = models.BooleanField(default=True, + verbose_name = pgettext_lazy("is the flow rule exception activated?","Active")) def __unicode__(self): - return "%s exception for '%s' to '%s' in '%s'" % ( - self.kind, - self.participation.user, self.flow_id, - self.participation.course) + # Translators: For FlowRuleException + return "%(kind)s exception for '%(user)s' to '%(flow_id)s' in '%(course)s'" % { + 'kind':self.kind, + 'user':self.participation.user, 'flow_id':self.flow_id, + 'course':self.participation.course} def clean(self): if (self.kind == flow_rule_kind.grading @@ -733,44 +904,65 @@ class FlowRuleException(models.Model): elif self.kind == flow_rule_kind.grading: validate_session_grading_rule(ctx, unicode(self), rule, tags) else: + # the rule refers to FlowRuleException rule raise ValidationError("invalid rule kind: "+self.kind) except ContentValidationError as e: + # the rule refers to FlowRuleException rule raise ValidationError("invalid existing_session_rules: "+str(e)) + class Meta: + verbose_name = "Flow rule exception" + verbose_name_plural = "Flow rule exceptions" + # }}} # {{{ grading class GradingOpportunity(models.Model): - course = models.ForeignKey(Course) + course = models.ForeignKey(Course, + verbose_name = 'Course ID') identifier = models.CharField(max_length=200, blank=False, null=False, + # Translators: format of identifier for GradingOpportunity help_text="A symbolic name for this grade. " - "lower_case_with_underscores, no spaces.") + "lower_case_with_underscores, no spaces.", + verbose_name = 'Grading opportunity ID') name = models.CharField(max_length=200, blank=False, null=False, - help_text="A human-readable identifier for the grade.") + # Translators: name for GradingOpportunity + help_text="A human-readable identifier for the grade.", + verbose_name = 'Grading opportunity name') flow_id = models.CharField(max_length=200, blank=True, null=True, help_text="Flow identifier that this grading opportunity " - "is linked to, if any") + "is linked to, if any", + verbose_name = 'Flow ID') aggregation_strategy = models.CharField(max_length=20, - choices=GRADE_AGGREGATION_STRATEGY_CHOICES) + choices=GRADE_AGGREGATION_STRATEGY_CHOICES, + # Translators: strategy on how the grading of mutiple sessioins are aggregated. + verbose_name = 'Aggregation strategy') - due_time = models.DateTimeField(default=None, blank=True, null=True) - creation_time = models.DateTimeField(default=now) + due_time = models.DateTimeField(default=None, blank=True, null=True, + verbose_name = 'Due time') + creation_time = models.DateTimeField(default=now, + verbose_name = 'Creation time') - shown_in_grade_book = models.BooleanField(default=True) - shown_in_student_grade_book = models.BooleanField(default=True) + shown_in_grade_book = models.BooleanField(default=True, + verbose_name = 'Shown in grade book') + shown_in_student_grade_book = models.BooleanField(default=True, + verbose_name = 'Shown in student grade book') class Meta: - verbose_name_plural = "grading opportunities" + verbose_name = "Grading opportunity" + verbose_name_plural = "Grading opportunities" ordering = ("course", "due_time", "identifier") unique_together = (("course", "identifier"),) def __unicode__(self): - return "%s (%s) in %s" % (self.name, self.identifier, self.course) + # Translators: For GradingOpportunity + return "%(opportunit_name)s (%(opportunit_id)s) in %(course)s" % { + 'opportunit_name':self.name, 'opportunit_id':self.identifier, 'course':self.course} class GradeChange(models.Model): @@ -781,39 +973,57 @@ class GradeChange(models.Model): identifier, where later grades with the same :attr:`attempt_id` supersede earlier ones. """ - opportunity = models.ForeignKey(GradingOpportunity) + opportunity = models.ForeignKey(GradingOpportunity, + verbose_name = 'Grading opportunity') - participation = models.ForeignKey(Participation) + participation = models.ForeignKey(Participation, + verbose_name = 'Participation') state = models.CharField(max_length=50, - choices=GRADE_STATE_CHANGE_CHOICES) + choices=GRADE_STATE_CHANGE_CHOICES, + # Translators: something like 'status'. + verbose_name = 'State') attempt_id = models.CharField(max_length=50, null=True, blank=True, default="main", + # Translators: help text of "attempt_id" in GradeChange class help_text="Grade changes are grouped by their 'attempt ID' " "where later grades with the same attempt ID supersede earlier " - "ones.") + "ones.", + verbose_name = 'Attempt ID') points = models.DecimalField(max_digits=10, decimal_places=2, - blank=True, null=True) - max_points = models.DecimalField(max_digits=10, decimal_places=2) + blank=True, null=True, + verbose_name = 'Points') + max_points = models.DecimalField(max_digits=10, decimal_places=2, + verbose_name = 'Max points') - comment = models.TextField(blank=True, null=True) + comment = models.TextField(blank=True, null=True, + verbose_name = 'Comment') - due_time = models.DateTimeField(default=None, blank=True, null=True) + due_time = models.DateTimeField(default=None, blank=True, null=True, + verbose_name = 'Due time') - creator = models.ForeignKey(User, null=True) - grade_time = models.DateTimeField(default=now, db_index=True) + creator = models.ForeignKey(User, null=True, + verbose_name = 'Creator') + grade_time = models.DateTimeField(default=now, db_index=True, + verbose_name = 'Grade time') flow_session = models.ForeignKey(FlowSession, null=True, blank=True, - related_name="grade_changes") + related_name="grade_changes", + verbose_name = 'Flow session') class Meta: + verbose_name = "Grade change" + verbose_name_plural = "Grade changes" ordering = ("opportunity", "participation", "grade_time") def __unicode__(self): - return "%s %s on %s" % (self.participation, self.state, - self.opportunity.name) + # Translators: information for GradeChange + return "%(participation)s %(state)s on %(opportunityname)s" % { + 'participation':self.participation, + 'state':self.state, + 'opportunityname':self.opportunity.name} def clean(self): if self.opportunity.course != self.participation.course: @@ -997,7 +1207,8 @@ def get_flow_grading_opportunity(course, flow_id, flow_desc, grading_rule): course=course, identifier=grading_rule.grade_identifier, defaults=dict( - name="Flow: %s" % flow_desc.title, + # Translators: name of flow with prefix "Flow" + name="Flow: %(flow_desc_title)s" % {"flow_desc_title":flow_desc.title}, flow_id=flow_id, aggregation_strategy=grading_rule.grade_aggregation_strategy, )) @@ -1010,11 +1221,16 @@ def get_flow_grading_opportunity(course, flow_id, flow_desc, grading_rule): # {{{ XMPP log class InstantMessage(models.Model): - participation = models.ForeignKey(Participation) - text = models.CharField(max_length=200) - time = models.DateTimeField(default=now) + participation = models.ForeignKey(Participation, + verbose_name = 'Participation') + text = models.CharField(max_length=200, + verbose_name = 'Text') + time = models.DateTimeField(default=now, + verbose_name = 'Time') class Meta: + verbose_name = "Instant message" + verbose_name_plural = "Instant messages" ordering = ("participation__course", "time") def __unicode__(self): diff --git a/course/page/base.py b/course/page/base.py index 5ea8387d22363b1e6a8691c705458205f7d316f8..484176399e598f5bfcc65297a211a1197a71ac82 100644 --- a/course/page/base.py +++ b/course/page/base.py @@ -530,7 +530,8 @@ class HumanTextFeedbackForm(StyledForm): self.fields["released"] = forms.BooleanField( initial=True, required=False, help_text="Whether the grade and feedback below are to be shown " - "to student") + "to student", + label="Released") self.fields["grade_percent"] = forms.FloatField( min_value=0, max_value=100 * MAX_EXTRA_CREDIT_FACTOR, @@ -538,7 +539,8 @@ class HumanTextFeedbackForm(StyledForm): required=False, # avoid unfortunate scroll wheel accidents reported by graders - widget=forms.TextInput) + widget=forms.TextInput, + label="Grade percent") if point_value is not None: self.fields["grade_points"] = forms.FloatField( @@ -549,7 +551,8 @@ class HumanTextFeedbackForm(StyledForm): required=False, # avoid unfortunate scroll wheel accidents reported by graders - widget=forms.TextInput) + widget=forms.TextInput, + label="Grade points") self.fields["feedback_text"] = forms.CharField( widget=forms.Textarea(), @@ -557,16 +560,19 @@ class HumanTextFeedbackForm(StyledForm): help_text=mark_safe("Feedback to be shown to student, using " "" - "RELATE-flavored Markdown")) + "RELATE-flavored Markdown"), + label="Feedback text") self.fields["notify"] = forms.BooleanField( initial=False, required=False, help_text="Checking this box and submitting the form " "will notify the participant " - "with a generic message containing the feedback text") + "with a generic message containing the feedback text", + label="Notify") self.fields["notes"] = forms.CharField( widget=forms.Textarea(), help_text="Internal notes, not shown to student", - required=False) + required=False, + label="Notes") def clean(self): grade_percent = self.cleaned_data.get("grade_percent") @@ -666,9 +672,9 @@ class PageBaseWithHumanTextFeedback(PageBase): from django.core.mail import send_mail from django.conf import settings - send_mail("[%s:%s] New notification" - % (page_context.course.identifier, - page_context.flow_session.flow_id), + send_mail("[%(identifier)s:%(flow_id)s] New notification" + % {'identifier':page_context.course.identifier, + 'flow_id':page_context.flow_session.flow_id}, message, settings.ROBOT_EMAIL_FROM, recipient_list=[ diff --git a/course/page/choice.py b/course/page/choice.py index b6adef27c208a99c47d183dc1399144bb492adca..96a2b35ea44550cc3cd9e9ae23f1463ec3038604 100644 --- a/course/page/choice.py +++ b/course/page/choice.py @@ -40,6 +40,8 @@ class ChoiceAnswerForm(StyledForm): super(ChoiceAnswerForm, self).__init__(*args, **kwargs) self.fields["choice"] = field + # Translators: "choice" in Choice Answer Form in a choice question. + self.fields["choice"].label = "Choice" # {{{ choice question @@ -104,8 +106,8 @@ class ChoiceQuestion(PageBaseWithTitle, PageBaseWithValue): try: choice = str(choice) except: - raise ValidationError("%s, choice %d: unable to convert to string" - % (location, choice_idx+1)) + raise ValidationError("%(location)s, choice %(id)d: unable to convert to string" + % {'location':location, 'id':choice_idx+1}) if choice.startswith(self.CORRECT_TAG): correct_choice_count += 1 @@ -115,8 +117,8 @@ class ChoiceQuestion(PageBaseWithTitle, PageBaseWithValue): remove_prefix(self.CORRECT_TAG, choice)) if correct_choice_count < 1: - raise ValidationError("%s: one or more correct answer(s) " - "expected, %d found" % (location, correct_choice_count)) + raise ValidationError("%(location)s: one or more correct answer(s) " + "expected, %(correct)d found" % {'location':location, 'correct':correct_choice_count}) def required_attrs(self): return super(ChoiceQuestion, self).required_attrs() + ( diff --git a/course/page/code.py b/course/page/code.py index 5888e6451ef878b5af3dd89c5b7f546925e28199..956c6e2b01c7e71e789541f03354013b10746d2b 100644 --- a/course/page/code.py +++ b/course/page/code.py @@ -54,7 +54,8 @@ class PythonCodeForm(StyledForm): self.fields["answer"] = forms.CharField(required=True, initial=initial_code, help_text=cm_help_text, - widget=cm_widget) + widget=cm_widget, + label="Answer") def clean(self): # FIXME Should try compilation diff --git a/course/page/text.py b/course/page/text.py index c2db93d8bada2a0b261519809142f1b7146ecc79..d83f386062870659ebdafc18a5cb5182dd042e76 100644 --- a/course/page/text.py +++ b/course/page/text.py @@ -91,7 +91,8 @@ class TextAnswerForm(StyledForm): self.fields["answer"] = forms.CharField( required=True, widget=widget, - help_text=help_text) + help_text=help_text, + label="Answer") def clean(self): cleaned_data = super(TextAnswerForm, self).clean() diff --git a/course/page/upload.py b/course/page/upload.py index e4aa0c10fe74d0b45171999e9bc2813be62ba54e..071b098e03d20af96f69a93d65876fb81789e3f4 100644 --- a/course/page/upload.py +++ b/course/page/upload.py @@ -39,7 +39,7 @@ from relate.utils import StyledForm # {{{ upload question class FileUploadForm(StyledForm): - uploaded_file = forms.FileField(required=True) + uploaded_file = forms.FileField(required=True,label='Uploaded file') def __init__(self, maximum_megabytes, mime_types, *args, **kwargs): super(FileUploadForm, self).__init__(*args, **kwargs) diff --git a/course/sandbox.py b/course/sandbox.py index aabf38891e26e18e877565c78cd3a5b2d9c30f30..831f4eb198155717060db4ee6b0b91ae17d2728e 100644 --- a/course/sandbox.py +++ b/course/sandbox.py @@ -58,7 +58,8 @@ class SandboxForm(forms.Form): widget=cm_widget, help_text=mark_safe( help_text + " Press Alt/Cmd+(Shift+)P to preview. " - + cm_help_text)) + + cm_help_text), + label="Content") self.helper.add_input( Submit( diff --git a/course/versioning.py b/course/versioning.py index b3b6fccd13d7dcd06ddf6ccc7f1db29406a5de7a..e2f6e02eb1d30af2d9ce4941a0b782de912703a3 100644 --- a/course/versioning.py +++ b/course/versioning.py @@ -31,6 +31,13 @@ import django.forms as forms from django.contrib.auth.decorators import login_required from django.core.exceptions import PermissionDenied, SuspiciousOperation from django.core.urlresolvers import reverse +from django.utils.translation import ( + ugettext_lazy as _ , + ugettext, + pgettext, + pgettext_lazy, + string_concat, + ) from django.db import transaction @@ -335,7 +342,8 @@ def run_course_update_command(request, pctx, command, new_sha, may_update): class GitUpdateForm(StyledForm): - new_sha = forms.CharField(required=True) + new_sha = forms.CharField(required=True, + label=pgettext_lazy("new git SHA for revision of course contents","New git SHA")) def __init__(self, may_update, previewing, *args, **kwargs): super(GitUpdateForm, self).__init__(*args, **kwargs) diff --git a/course/views.py b/course/views.py index 93cae9604f46dc02aafefafcf52938177fdc0dcd..f3f2a565d4a39baa7faa4827dc2a69c4b60e4fe1 100644 --- a/course/views.py +++ b/course/views.py @@ -34,6 +34,17 @@ import django.views.decorators.http as http_dec from django import http from django.utils.safestring import mark_safe from django.db import transaction +from django.utils import six +from django.utils.translation import ( + ugettext_lazy as _ , + ugettext, + string_concat, + pgettext, + pgettext_lazy, + ) +from django.utils.functional import lazy + +mark_safe_lazy = lazy(mark_safe, six.text_type) from django.views.decorators.cache import cache_control @@ -211,7 +222,8 @@ def get_repo_file(request, course_identifier, commit_sha, path): class FakeTimeForm(StyledForm): time = forms.DateTimeField( widget=DateTimePicker( - options={"format": "YYYY-MM-DD HH:mm", "sideBySide": True})) + options={"format": "YYYY-MM-DD HH:mm", "sideBySide": True}), + label='Time') def __init__(self, *args, **kwargs): super(FakeTimeForm, self).__init__(*args, **kwargs) @@ -291,9 +303,11 @@ class InstantFlowRequestForm(StyledForm): self.fields["flow_id"] = forms.ChoiceField( choices=[(fid, fid) for fid in flow_ids], - required=True) + required=True, + label="Flow ID") self.fields["duration_in_minutes"] = forms.IntegerField( - required=True, initial=20) + required=True, initial=20, + label="Duration in minutes") self.helper.add_input( Submit("add", "Add", css_class="col-lg-offset-2")) @@ -365,7 +379,8 @@ class FlowTestForm(StyledForm): self.fields["flow_id"] = forms.ChoiceField( choices=[(fid, fid) for fid in flow_ids], - required=True) + required=True, + label="Flow ID") self.helper.add_input( Submit("test", mark_safe("Go »"), css_class="col-lg-offset-2")) @@ -423,10 +438,12 @@ class ExceptionStage1Form(StyledForm): ) .order_by("user__last_name")), required=True, - help_text="Select participant for whom exception is to be granted.") + help_text="Select participant for whom exception is to be granted.", + label="Participant") self.fields["flow_id"] = forms.ChoiceField( choices=[(fid, fid) for fid in flow_ids], - required=True) + required=True, + label="Flow ID") self.helper.add_input( Submit( @@ -483,7 +500,8 @@ class CreateSessionForm(StyledForm): choices=session_tag_choices, initial=default_tag, help_text="If you click 'Create session', this tag will be " - "applied to the new session.") + "applied to the new session.", + label="Access rules tag for new session") if create_session_is_override: self.helper.add_input( @@ -504,7 +522,8 @@ class ExceptionStage2Form(StyledForm): (session.id, strify_session_for_exception(session)) for session in sessions), help_text="The rules that currently apply to selected session " - "will provide the default values for the rules on the next page.") + "will provide the default values for the rules on the next page.", + label="Session") self.helper.add_input(Submit("next", mark_safe("Next »"), css_class="col-lg-offset-2")) @@ -622,6 +641,7 @@ class ExceptionStage3Form(StyledForm): options={"format": "YYYY-MM-DD HH:mm", "sideBySide": True, "showClear": True}), required=False, + label=pgettext_lazy("time when access expires","Access expires"), help_text="At the specified time, the special access granted below " "will expire " "and revert to being the same as for the rest of the class. " @@ -644,7 +664,8 @@ class ExceptionStage3Form(StyledForm): [(tag, tag) for tag in tags], initial=(base_session_tag if base_session_tag is not None - else NONE_SESSION_TAG)) + else NONE_SESSION_TAG), + label="Set access rules tag") self.fields["restrict_to_same_tag"] = forms.BooleanField( label="Exception only applies to sessions with the above tag", required=False, @@ -666,24 +687,28 @@ class ExceptionStage3Form(StyledForm): self.fields["due_same_as_access_expiration"] = forms.BooleanField( required=False, help_text="If set, the 'Due' field will be " "disregarded.", - initial=default_data.get("due_same_as_access_expiration") or False) + initial=default_data.get("due_same_as_access_expiration") or False, + label="Due same as access expiration") self.fields["due"] = forms.DateTimeField( widget=DateTimePicker( options={"format": "YYYY-MM-DD HH:mm", "sideBySide": True}), required=False, - help_text="The due date shown to the student. Also, the " + help_text="The due time shown to the student. Also, the " "time after which " "any session under these rules is subject to expiration.", - initial=default_data.get("due")) + initial=default_data.get("due"), + label="Due time") self.fields["credit_percent"] = forms.IntegerField(required=False, - initial=default_data.get("credit_percent")) + initial=default_data.get("credit_percent"), + label="Credit percent") layout.append(Div("due_same_as_access_expiration", "due", "credit_percent", css_class="well")) self.fields["comment"] = forms.CharField( widget=forms.Textarea, required=True, - initial=default_data.get("comment")) + initial=default_data.get("comment"), + label="Comment") layout.append("comment") diff --git a/relate/settings.py b/relate/settings.py index 614053c433588879e0763f80a129486c8d87554e..27e069311f5340db51eebd9e0e9da3cc7e77180c 100644 --- a/relate/settings.py +++ b/relate/settings.py @@ -48,6 +48,7 @@ INSTALLED_APPS = ( MIDDLEWARE_CLASSES = ( "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.locale.LocaleMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", @@ -173,3 +174,7 @@ TEMPLATES = [ } }, ] + +LOCALE_PATHS = ( + BASE_DIR+ '/locale', +)