diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d5871a132d789c5828b0de1ad95d09248f01c8f4..5aa7f869ebe792ab2a04ec7a08b8b1fa98685511 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,9 @@ jobs: run: poetry install if: steps.cache.outputs.cache-hit != 'true' - name: "Flake8" - run: poetry run flake8 relate course accounts tests + run: | + poetry run flake8 relate course accounts + poetry run flake8 --extend-ignore Q000 tests - name: "Mypy" run: poetry run mypy relate course - name: "Safety" diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index daa2241ec97f597a764c7cf6bc17b950433a5495..a12a7a38cbc752300b5a80cf34905658c674973f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -53,7 +53,9 @@ setup: flake8: <<: *quality - script: poetry run flake8 relate course accounts tests + script: | + poetry run flake8 relate course accounts + poetry run flake8 --extend-ignore Q000 tests mypy: <<: *quality diff --git a/accounts/__init__.py b/accounts/__init__.py index 8319823191b94c101df51a5f8af026671f85f567..bc2bb4f01ef75893501a791beae6b1080bfa28d4 100644 --- a/accounts/__init__.py +++ b/accounts/__init__.py @@ -1 +1 @@ -default_app_config = 'accounts.apps.AccountsConfig' +default_app_config = "accounts.apps.AccountsConfig" diff --git a/accounts/apps.py b/accounts/apps.py index 951b6f6d7c32cf4ec682383c106a3726bbc45fa4..173b9392b95e3209da7ce8df3fe6d1d053f0b604 100644 --- a/accounts/apps.py +++ b/accounts/apps.py @@ -3,6 +3,6 @@ from django.utils.translation import gettext_lazy as _ class AccountsConfig(AppConfig): - name = 'accounts' + name = "accounts" # for translation of the name of "Accounts" app displayed in admin. verbose_name = _("Accounts") diff --git a/accounts/models.py b/accounts/models.py index 1476fcef548e474c72bf28fbd9f579c8a6ac01b9..e46c6397c16bd806bd9d0018834d68d6cf11a738 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -39,73 +39,73 @@ from course.constants import USER_STATUS_CHOICES class User(AbstractBaseUser, PermissionsMixin): username = models.CharField( - _('username'), + _("username"), max_length=30, unique=True, help_text=_( - 'Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.'), + "Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only."), validators=[ASCIIUsernameValidator()], error_messages={ - 'unique': _("A user with that username already exists."), + "unique": _("A user with that username already exists."), }, ) - first_name = models.CharField(_('first name'), max_length=100, blank=True) - last_name = models.CharField(_('last name'), max_length=100, blank=True) - email = models.EmailField(_('email address'), blank=True, + first_name = models.CharField(_("first name"), max_length=100, blank=True) + last_name = models.CharField(_("last name"), max_length=100, blank=True) + email = models.EmailField(_("email address"), blank=True, max_length=100) name_verified = models.BooleanField( - _('Name verified'), + _("Name verified"), default=False, help_text=_( - 'Indicates that this user\'s name has been verified ' - 'as being associated with the individual able to sign ' - 'in to this account.' + "Indicates that this user's name has been verified " + "as being associated with the individual able to sign " + "in to this account." ), ) is_active = models.BooleanField( pgettext_lazy("User status", "active"), default=True, help_text=_( - 'Designates whether this user should be treated as active. ' - 'Unselect this instead of deleting accounts.' + "Designates whether this user should be treated as active. " + "Unselect this instead of deleting accounts." ), ) - date_joined = models.DateTimeField(_('date joined'), default=timezone.now) + date_joined = models.DateTimeField(_("date joined"), default=timezone.now) objects = UserManager() is_staff = models.BooleanField( - _('staff status'), + _("staff status"), default=False, - help_text=_('Designates whether the user can log into this admin site.'), + help_text=_("Designates whether the user can log into this admin site."), ) institutional_id = models.CharField(max_length=100, - verbose_name=_('Institutional ID'), + verbose_name=_("Institutional ID"), blank=True, null=True, unique=True, db_index=True) institutional_id_verified = models.BooleanField( - _('Institutional ID verified'), + _("Institutional ID verified"), default=False, help_text=_( - 'Indicates that this user\'s institutional ID has been verified ' - 'as being associated with the individual able to log ' - 'in to this account.' + "Indicates that this user's institutional ID has been verified " + "as being associated with the individual able to log " + "in to this account." ), ) status = models.CharField(max_length=50, choices=USER_STATUS_CHOICES, - verbose_name=_('User status'), + verbose_name=_("User status"), null=True) sign_in_key = models.CharField(max_length=50, help_text=_("The sign in token sent out in email."), null=True, unique=True, db_index=True, blank=True, # Translators: the sign in token of the user. - verbose_name=_('Sign in key')) + verbose_name=_("Sign in key")) key_time = models.DateTimeField(default=None, null=True, blank=True, help_text=_("The time stamp of the sign in token."), # Translators: the time when the token is sent out. - verbose_name=_('Key time')) + verbose_name=_("Key time")) editor_mode = models.CharField(max_length=20, help_text=_("Which key bindings you prefer when editing " @@ -122,12 +122,12 @@ class User(AbstractBaseUser, PermissionsMixin): # Translators: the text editor used by participants verbose_name=_("Editor mode")) - USERNAME_FIELD = 'username' - REQUIRED_FIELDS = ['email'] + USERNAME_FIELD = "username" + REQUIRED_FIELDS = ["email"] class Meta: - verbose_name = _('user') - verbose_name_plural = _('users') + verbose_name = _("user") + verbose_name_plural = _("users") def get_full_name(self, allow_blank=True, force_verbose_blank=False): if (not allow_blank @@ -147,7 +147,7 @@ class User(AbstractBaseUser, PermissionsMixin): Returns the first_name plus the last_name, with a space in between. """ - return '%s %s' % ( + return "%s %s" % ( verbose_blank(first_name), verbose_blank(last_name)) from accounts.utils import relate_user_method_settings @@ -189,11 +189,11 @@ class User(AbstractBaseUser, PermissionsMixin): return result def get_short_name(self): - "Returns the short name for the user." + """Returns the short name for the user.""" return self.first_name def get_email_appellation(self): - "Return the appellation of the receiver in email." + """Return the appellation of the receiver in email.""" from accounts.utils import relate_user_method_settings priority_list = ( diff --git a/accounts/utils.py b/accounts/utils.py index c796140f3e999d96350edfd8670072681c3c08f9..56515c494bc0839fa2843e645eef599f3f31ca91 100644 --- a/accounts/utils.py +++ b/accounts/utils.py @@ -248,7 +248,7 @@ class RelateUserMethodSettingsInitializer(object): "method": relate_user_full_name_format_method, "err_type": type(e).__name__, "err_str": str(e), - 'format_exc': format_exc()} + "format_exc": format_exc()} ), id="relate_user_full_name_format_method.W003" )) diff --git a/course/__init__.py b/course/__init__.py index 10308e50109453da49fbc2072499f878365a0e17..0db938c12993c08b0b7a4f81ea76f2e845c382ec 100644 --- a/course/__init__.py +++ b/course/__init__.py @@ -1 +1 @@ -default_app_config = 'course.apps.CourseConfig' +default_app_config = "course.apps.CourseConfig" diff --git a/course/admin.py b/course/admin.py index 608f5f7bf5d8418d70ab8f061f93a9eec9ade6b5..28d0ca0afa92c7623c29aab84191cdf7dea4f43d 100644 --- a/course/admin.py +++ b/course/admin.py @@ -96,7 +96,7 @@ def _filter_related_only(filter_arg): class UnsafePasswordInput(forms.TextInput): # This sends passwords back to the user--not ideal, but OK for the XMPP # password. - input_type = 'password' + input_type = "password" class CourseAdminForm(forms.ModelForm): @@ -553,14 +553,14 @@ class FlowPageVisitGradeInline(admin.TabularInline): class HasAnswerListFilter(admin.SimpleListFilter): - title = 'has answer' + title = "has answer" - parameter_name = 'has_answer' + parameter_name = "has_answer" def lookups(self, request, model_admin): return ( - ('y', _('Yes')), - ('n', _('No')), + ("y", _("Yes")), + ("n", _("No")), ) def queryset(self, request, queryset): diff --git a/course/apps.py b/course/apps.py index 07c4f578ec9856f36082cd4fdae368ded10fda35..9e065efe9eeff1cc1f0590cc496ad46c49ad6c6e 100644 --- a/course/apps.py +++ b/course/apps.py @@ -4,7 +4,7 @@ from relate.checks import register_startup_checks_extra, register_startup_checks class CourseConfig(AppConfig): - name = 'course' + name = "course" # for translation of the name of "Course" app displayed in admin. verbose_name = _("Course module") diff --git a/course/auth.py b/course/auth.py index 1cadbe278380401589c2057dce0872ee94813086..4f2d04bfcf013c851f67c0af152dcfdf9a3de7f2 100644 --- a/course/auth.py +++ b/course/auth.py @@ -127,8 +127,8 @@ class ImpersonateMiddleware(object): self.get_response = get_response def __call__(self, request): - if 'impersonate_id' in request.session: - imp_id = request.session['impersonate_id'] + if "impersonate_id" in request.session: + imp_id = request.session["impersonate_id"] impersonee = None try: @@ -159,10 +159,10 @@ class ImpersonateMiddleware(object): class UserSearchWidget(ModelSelect2Widget): model = User search_fields = [ - 'username__icontains', - 'email__icontains', - 'first_name__icontains', - 'last_name__icontains', + "username__icontains", + "email__icontains", + "first_name__icontains", + "last_name__icontains", ] def label_from_instance(self, u): @@ -230,13 +230,13 @@ def impersonate(request): qset = (User.objects .filter(pk__in=impersonable_user_qset.values_list("pk", flat=True)) .order_by("last_name", "first_name", "username")) - if request.method == 'POST': + if request.method == "POST": form = ImpersonateForm(request.POST, impersonable_qset=qset) if form.is_valid(): impersonee = form.cleaned_data["user"] - request.session['impersonate_id'] = impersonee.id - request.session['relate_impersonation_header'] = form.cleaned_data[ + request.session["impersonate_id"] = impersonee.id + request.session["relate_impersonation_header"] = form.cleaned_data[ "add_impersonation_header"] # Because we'll likely no longer have access to this page. @@ -285,7 +285,7 @@ def stop_impersonating(request): _("Not currently impersonating anyone.")) return http.JsonResponse({}) - del request.session['impersonate_id'] + del request.session["impersonate_id"] messages.add_message(request, messages.INFO, _("No longer impersonating anyone.")) return http.JsonResponse({"result": "success"}) @@ -317,7 +317,7 @@ def make_sign_in_key(user): def logout_confirmation_required( func=None, redirect_field_name=REDIRECT_FIELD_NAME, - logout_confirmation_url='relate-logout-confirmation'): + logout_confirmation_url="relate-logout-confirmation"): """ Decorator for views that checks that no user is logged in. If a user is currently logged in, redirect him/her to the logout @@ -362,7 +362,7 @@ class EmailedTokenBackend(object): @logout_confirmation_required def sign_in_choice(request, redirect_field_name=REDIRECT_FIELD_NAME): redirect_to = request.POST.get(redirect_field_name, - request.GET.get(redirect_field_name, '')) + request.GET.get(redirect_field_name, "")) next_uri = "" if redirect_to: next_uri = "?%s=%s" % (redirect_field_name, redirect_to) @@ -400,7 +400,7 @@ def sign_in_by_user_pw(request, redirect_field_name=REDIRECT_FIELD_NAME): return redirect("relate-sign_in_choice") redirect_to = request.POST.get(redirect_field_name, - request.GET.get(redirect_field_name, '')) + request.GET.get(redirect_field_name, "")) if request.method == "POST": form = LoginForm(request, data=request.POST) @@ -427,9 +427,9 @@ def sign_in_by_user_pw(request, redirect_field_name=REDIRECT_FIELD_NAME): next_uri = "?%s=%s" % (redirect_field_name, redirect_to) context = { - 'form': form, + "form": form, redirect_field_name: redirect_to, - 'next_uri': next_uri, + "next_uri": next_uri, } return TemplateResponse(request, "course/login.html", context) @@ -459,7 +459,7 @@ def sign_up(request): raise SuspiciousOperation( _("self-registration is not enabled")) - if request.method == 'POST': + if request.method == "POST": form = SignUpForm(request.POST) if form.is_valid(): if get_user_model().objects.filter( @@ -533,7 +533,7 @@ def sign_up(request): class ResetPasswordFormByEmail(StyledForm): email = forms.EmailField(required=True, label=_("Email"), - max_length=User._meta.get_field('email').max_length) + max_length=User._meta.get_field("email").max_length) def __init__(self, *args, **kwargs): super(ResetPasswordFormByEmail, self).__init__(*args, **kwargs) @@ -556,7 +556,7 @@ class ResetPasswordFormByInstid(StyledForm): def masked_email(email): # return a masked email address - at = email.find('@') + at = email.find("@") return email[:2] + "*" * (len(email[3:at])-1) + email[at-1:] @@ -568,7 +568,7 @@ def reset_password(request, field="email"): # return form class by string of class name ResetPasswordForm = globals()["ResetPasswordFormBy" + field.title()] # noqa - if request.method == 'POST': + if request.method == "POST": form = ResetPasswordForm(request.POST) user = None if form.is_valid(): @@ -709,7 +709,7 @@ def reset_password_stage2(request, user_id, sign_in_key): messages.add_message(request, messages.ERROR, _("Account does not exist.")) raise PermissionDenied(_("invalid sign-in token")) - if request.method == 'POST': + if request.method == "POST": form = ResetPasswordStage2Form(request.POST) if form.is_valid(): from django.contrib.auth import authenticate, login @@ -761,7 +761,7 @@ def reset_password_stage2(request, user_id, sign_in_key): class SignInByEmailForm(StyledForm): email = forms.EmailField(required=True, label=_("Email"), # For now, until we upgrade to a custom user model. - max_length=User._meta.get_field('email').max_length) + max_length=User._meta.get_field("email").max_length) def __init__(self, *args, **kwargs): super(SignInByEmailForm, self).__init__(*args, **kwargs) @@ -777,7 +777,7 @@ def sign_in_by_email(request): _("Email-based sign-in is not being used")) return redirect("relate-sign_in_choice") - if request.method == 'POST': + if request.method == "POST": form = SignInByEmailForm(request.POST) if form.is_valid(): email = form.cleaned_data["email"] @@ -892,7 +892,7 @@ class UserForm(StyledModelForm): "editor_mode") def __init__(self, *args, **kwargs): - self.is_inst_id_locked = kwargs.pop('is_inst_id_locked') + self.is_inst_id_locked = kwargs.pop("is_inst_id_locked") super(UserForm, self).__init__(*args, **kwargs) if self.instance.name_verified: @@ -1072,7 +1072,7 @@ def sign_out_confirmation(request, redirect_field_name=REDIRECT_FIELD_NAME): return redirect("relate-home") redirect_to = request.POST.get(redirect_field_name, - request.GET.get(redirect_field_name, '')) + request.GET.get(redirect_field_name, "")) next_uri = "" if redirect_to: @@ -1090,7 +1090,7 @@ def sign_out(request, redirect_field_name=REDIRECT_FIELD_NAME): return redirect("relate-home") redirect_to = request.POST.get(redirect_field_name, - request.GET.get(redirect_field_name, '')) + request.GET.get(redirect_field_name, "")) response = None if settings.RELATE_SIGN_IN_BY_SAML2_ENABLED: @@ -1266,7 +1266,7 @@ def auth_course_with_token(method, func, request, realm = _("Relate direct git access for {}".format(course_identifier)) response = http.HttpResponse("Forbidden: " + str(e), content_type="text/plain") - response['WWW-Authenticate'] = 'Basic realm="%s"' % (realm) + response["WWW-Authenticate"] = 'Basic realm="%s"' % (realm) response.status_code = 401 return response @@ -1352,7 +1352,7 @@ def manage_authentication_tokens(pctx): from course.views import get_now_or_fake_time now_datetime = get_now_or_fake_time(request) - if request.method == 'POST': + if request.method == "POST": form = AuthenticationTokenForm(pctx.participation, request.POST) revoke_prefix = "revoke_" diff --git a/course/calendar.py b/course/calendar.py index 0de574f1670eb13b326f7e8ade8b0bfdf5cc24e8..edf2c03dd863413dca0e196f8b442a017d7dddb8 100644 --- a/course/calendar.py +++ b/course/calendar.py @@ -52,7 +52,7 @@ class ListTextWidget(forms.TextInput): super(ListTextWidget, self).__init__(*args, **kwargs) self._name = name self._list = data_list - self.attrs.update({'list': 'list__%s' % self._name}) + self.attrs.update({"list": "list__%s" % self._name}) def render(self, name, value, attrs=None, renderer=None): text_html = super(ListTextWidget, self).render( @@ -60,7 +60,7 @@ class ListTextWidget(forms.TextInput): data_list = '' % self._name for item in self._list: data_list += '' % (item[0], item[1]) - data_list += '' + data_list += "" return (text_html + data_list) @@ -88,7 +88,7 @@ class RecurringEventForm(StyledForm): shown_in_calendar = forms.BooleanField( required=False, initial=True, - label=_('Shown in calendar')) + label=_("Shown in calendar")) interval = forms.ChoiceField(required=True, choices=( ("weekly", _("Weekly")), @@ -110,7 +110,7 @@ class RecurringEventForm(StyledForm): Event.objects.filter( course__identifier=course_identifier) .values_list("kind", flat=True))] - self.fields['kind'].widget = ListTextWidget(data_list=exist_event_choices, + self.fields["kind"].widget = ListTextWidget(data_list=exist_event_choices, name="event_choices") self.helper.add_input( @@ -145,7 +145,7 @@ def _create_recurring_events_backend(course, time, kind, starting_ordinal, inter if Event.objects.filter(course=course, kind=kind, ordinal=ordinal).count(): raise EventAlreadyExists( _("'%(exist_event)s' already exists") - % {'exist_event': evt}) + % {"exist_event": evt}) evt.save() @@ -272,7 +272,7 @@ class RenumberEventsForm(StyledForm): renumberable_event_kinds = set(Event.objects.filter( course__identifier=self.course_identifier, ordinal__isnull=False).values_list("kind", flat=True)) - self.fields['kind'].choices = tuple( + self.fields["kind"].choices = tuple( (kind, kind) for kind in renumberable_event_kinds) self.helper.add_input( diff --git a/course/content.py b/course/content.py index f6c18c203535e531df4addc690ac8e1f030b4697..25eb5748cc65acb306faeeeb44ebf07bad3c7626 100644 --- a/course/content.py +++ b/course/content.py @@ -227,7 +227,7 @@ def get_repo_blob(repo, full_name, commit_sha, allow_tree=True): names = os.path.normpath(full_name).split(os.sep) # Allow non-ASCII file name - full_name_bytes = full_name.encode('utf-8') + full_name_bytes = full_name.encode("utf-8") try: tree_sha = dul_repo[commit_sha].tree @@ -469,7 +469,7 @@ class GitTemplateLoader(BaseTemplateLoader): except ObjectDoesNotExist: raise TemplateNotFound(template) - source = data.decode('utf-8') + source = data.decode("utf-8") def is_up_to_date(): # There's not much point to caching here, because we create @@ -647,10 +647,10 @@ def get_yaml_from_repo(repo, full_name, commit_sha, cached=True): def _attr_to_string(key, val): if val is None: return key - elif "\"" in val: + elif '"' in val: return "%s='%s'" % (key, val) else: - return "%s=\"%s\"" % (key, val) + return '%s="%s"' % (key, val) class TagProcessingHTMLParser(html_parser.HTMLParser): @@ -1417,9 +1417,9 @@ def get_flow_page_desc(flow_id, flow_desc, group_id, page_id): raise ObjectDoesNotExist( _("page '%(group_id)s/%(page_id)s' in flow '%(flow_id)s'") % { - 'group_id': group_id, - 'page_id': page_id, - 'flow_id': flow_id + "group_id": group_id, + "page_id": page_id, + "flow_id": flow_id }) # }}} @@ -1433,7 +1433,7 @@ class ClassNotFoundError(RuntimeError): def import_class(name): # type: (Text) -> type - components = name.split('.') + components = name.split(".") if len(components) < 2: # need at least one module plus class name @@ -1487,7 +1487,7 @@ def get_flow_page_class(repo, typename, commit_sha): module_dict = {} # type: Dict - exec(compile(module_code, module_name, 'exec'), module_dict) + exec(compile(module_code, module_name, "exec"), module_dict) try: return module_dict[classname] diff --git a/course/enrollment.py b/course/enrollment.py index 9d419fe052b2990b44e7be7b149ba32ef655ef71..8c7e27c9226467bde2d738c3f4a88c681cb54309 100644 --- a/course/enrollment.py +++ b/course/enrollment.py @@ -456,7 +456,7 @@ class BulkPreapprovalsForm(StyledForm): self.fields["preapproval_data"] = forms.CharField( required=True, widget=forms.Textarea, help_text=_("Enter fully qualified data according to the " - "\"Preapproval type\" you selected, one per line."), + "'Preapproval type' you selected, one per line."), label=_("Preapproval data")) self.helper.add_input( @@ -541,9 +541,9 @@ def create_preapprovals(pctx): "%(n_exist)d already existed, " "%(n_requested_approved)d pending requests approved.") % { - 'n_created': created_count, - 'n_exist': exist_count, - 'n_requested_approved': pending_approved_count + "n_created": created_count, + "n_exist": exist_count, + "n_requested_approved": pending_approved_count }) return redirect("relate-course_page", pctx.course.identifier) @@ -1045,7 +1045,7 @@ def edit_participation(pctx, participation_id): if participation.course.id != pctx.course.id: raise SuspiciousOperation("may not edit participation in different course") - if request.method == 'POST': + if request.method == "POST": form = EditParticipationForm( add_new, pctx, request.POST, instance=participation) reset_form = False diff --git a/course/exam.py b/course/exam.py index cbc55980f8c5f088a6fb59db2f8c1f9883b155fa..f1708c6e0df5d155a98e36efd672dc2e78508eab 100644 --- a/course/exam.py +++ b/course/exam.py @@ -280,8 +280,8 @@ class BatchIssueTicketsForm(StyledForm): dependencies=("xml",), interaction_mode=editor_mode) - help_text = (gettext("Enter " + help_text = (gettext('Enter ' "RELATE markup containing Django template statements to render " "your exam tickets. tickets contains a list of " "data structures " diff --git a/course/flow.py b/course/flow.py index dc8c54d7db1a1942f46933599a3aa66c1e66139c..8d923ea9435578c6a85343667a9ac6ba0c5cfa37 100644 --- a/course/flow.py +++ b/course/flow.py @@ -1075,8 +1075,8 @@ def expire_flow_session( raise ValueError( _("invalid expiration mode '%(mode)s' on flow session ID " "%(session_id)d") % { - 'mode': flow_session.expiration_mode, - 'session_id': flow_session.id}) + "mode": flow_session.expiration_mode, + "session_id": flow_session.id}) def get_flow_session_attempt_id(flow_session): @@ -1111,8 +1111,8 @@ def grade_flow_session( comment = ( # Translators: grade flow: calculating grade. _("Counted at %(percent).1f%% of %(point).1f points") % { - 'percent': grading_rule.credit_percent, - 'point': points}) + "percent": grading_rule.credit_percent, + "point": points}) points = points * grading_rule.credit_percent / 100 flow_session.points = points @@ -1214,8 +1214,8 @@ def reopen_session( session.append_comment( _("Session reopened at %(now)s, previous completion time " "was '%(complete_time)s'.") % { - 'now': format_datetime_local(now_datetime), - 'complete_time': format_datetime_local( + "now": format_datetime_local(now_datetime), + "complete_time": format_datetime_local( as_local_time(session.completion_time)) }) @@ -1316,7 +1316,7 @@ def regrade_session( with transaction.atomic(): session.append_comment( _("Session regraded at %(time)s.") % { - 'time': format_datetime_local(now_datetime) + "time": format_datetime_local(now_datetime) }) session.save() @@ -1346,7 +1346,7 @@ def recalculate_session_grade(repo, course, session): now_datetime = local_now() session.append_comment( _("Session grade recomputed at %(time)s.") % { - 'time': format_datetime_local(now_datetime) + "time": format_datetime_local(now_datetime) }) session.save() @@ -1752,7 +1752,7 @@ def create_flow_page_visit(request, flow_session, page_data): visit = FlowPageVisit( flow_session=flow_session, page_data=page_data, - remote_address=request.META['REMOTE_ADDR'], + remote_address=request.META["REMOTE_ADDR"], user=user, is_submitted_answer=None) @@ -2079,8 +2079,8 @@ def view_flow_page(pctx, flow_session_id, page_ordinal): # Wrappers used by JavaScript template (tmpl) so as not to # conflict with Django template's tag wrapper - "JQ_OPEN": '{%', - 'JQ_CLOSE': '%}', + "JQ_OPEN": "{%", + "JQ_CLOSE": "%}", } if fpctx.page.expects_answer() and fpctx.page.is_answer_gradable(): @@ -2199,7 +2199,7 @@ def post_flow_page( answer_visit = FlowPageVisit() answer_visit.flow_session = flow_session answer_visit.page_data = fpctx.page_data - answer_visit.remote_address = request.META['REMOTE_ADDR'] + answer_visit.remote_address = request.META["REMOTE_ADDR"] answer_data = answer_visit.answer = fpctx.page.answer_data( page_context, fpctx.page_data.data, @@ -2337,9 +2337,9 @@ def send_email_about_flow_page(pctx, flow_session_id, page_ordinal): review_url = reverse( "relate-view_flow_page", - kwargs={'course_identifier': pctx.course.identifier, - 'flow_session_id': flow_session_id, - 'page_ordinal': page_ordinal + kwargs={"course_identifier": pctx.course.identifier, + "flow_session_id": flow_session_id, + "page_ordinal": page_ordinal } ) @@ -2408,10 +2408,10 @@ def send_email_about_flow_page(pctx, flow_session_id, page_ordinal): "[%(identifier)s:%(flow_id)s--%(page_id)s] ", _("Interaction request from %(username)s")) % { - 'identifier': pctx.course_identifier, - 'flow_id': flow_session_id, - 'page_id': page_id, - 'username': username + "identifier": pctx.course_identifier, + "flow_id": flow_session_id, + "page_id": page_id, + "username": username }, body=message, from_email=from_email, @@ -2680,9 +2680,9 @@ def finish_flow_session_view(pctx, flow_session_id): msg = EmailMessage( string_concat("[%(identifier)s:%(flow_id)s] ", _("Submission by %(participation_desc)s")) - % {'participation_desc': participation_desc, - 'identifier': fctx.course.identifier, - 'flow_id': flow_session.flow_id}, + % {"participation_desc": participation_desc, + "identifier": fctx.course.identifier, + "flow_id": flow_session.flow_id}, message, getattr(settings, "NOTIFICATION_EMAIL_FROM", settings.ROBOT_EMAIL_FROM), @@ -2882,7 +2882,7 @@ def view_unsubmit_flow_page(pctx, flow_session_id, page_ordinal): return redirect("relate-view_flow_page", pctx.course.identifier, flow_session_id, page_ordinal) - if request.method == 'POST': + if request.method == "POST": form = UnsubmitFlowPageForm(request.POST) if form.is_valid(): if "submit" in request.POST: @@ -2940,7 +2940,7 @@ def purge_page_view_data(request): purgeable_courses = get_pv_purgeable_courses_for_user_qs(request.user) if not purgeable_courses.count(): raise PermissionDenied() - if request.method == 'POST': + if request.method == "POST": form = PurgePageViewData(request.user, request.POST) if form.is_valid(): if "submit" in request.POST: diff --git a/course/grades.py b/course/grades.py index 6364d2a0043ea23e1fe05a3b47328fdc991801ce..b4df32b5b96f797f6677177ca2eb5466a06a4bd0 100644 --- a/course/grades.py +++ b/course/grades.py @@ -312,7 +312,7 @@ def export_gradebook_csv(pctx): import csv - fieldnames = ['user_name', 'last_name', 'first_name'] + [ + fieldnames = ["user_name", "last_name", "first_name"] + [ gopp.identifier for gopp in grading_opps] writer = csv.writer(csvfile) @@ -330,7 +330,7 @@ def export_gradebook_csv(pctx): response = http.HttpResponse( csvfile.getvalue().encode("utf-8"), content_type="text/plain; charset=utf-8") - response['Content-Disposition'] = ( + response["Content-Disposition"] = ( 'attachment; filename="grades-%s.csv"' % pctx.course.identifier) return response @@ -1144,7 +1144,7 @@ class ImportGradesForm(StyledForm): header_count) if not importable: - self.add_error('file', err_msg) + self.add_error("file", err_msg) class ParticipantNotFound(ValueError): @@ -1324,8 +1324,8 @@ def csv_to_grade_changes( "%(participation)s: %(updated)s ", _("updated") ) % { - 'participation': gchange.participation, - 'updated': ", ".join(updated)}) + "participation": gchange.participation, + "updated": ", ".join(updated)}) result.append(gchange) else: @@ -1388,8 +1388,8 @@ def import_grades(pctx): if total_count != len(grade_changes): messages.add_message(pctx.request, messages.INFO, _("%(total)d grades found, %(unchaged)d unchanged.") - % {'total': total_count, - 'unchaged': total_count - len(grade_changes)}) + % {"total": total_count, + "unchaged": total_count - len(grade_changes)}) from django.template.loader import render_to_string @@ -1616,7 +1616,7 @@ def download_all_submissions(pctx, flow_id): response = http.HttpResponse( bio.getvalue(), content_type="application/zip") - response['Content-Disposition'] = ( + response["Content-Disposition"] = ( 'attachment; filename="submissions_%s_%s_%s_%s_%s.zip"' % (pctx.course.identifier, flow_id, group_id, page_id, now().date().strftime("%Y-%m-%d"))) @@ -1684,7 +1684,7 @@ def edit_grading_opportunity(pctx, opportunity_id): raise SuspiciousOperation( "may not edit grading opportunity in different course") - if request.method == 'POST': + if request.method == "POST": form = EditGradingOpportunityForm(add_new, request.POST, instance=gopp) if form.is_valid(): diff --git a/course/grading.py b/course/grading.py index 20d65693ebbbf01ed58eca84dae8d7d1ff8ff438..0286776c74b0d88d156469e86e0109c6f1cb1f24 100644 --- a/course/grading.py +++ b/course/grading.py @@ -396,8 +396,8 @@ def grade_flow_page(pctx, flow_session_id, page_ordinal): # Wrappers used by JavaScript template (tmpl) so as not to # conflict with Django template's tag wrapper - "JQ_OPEN": '{%', - 'JQ_CLOSE': '%}', + "JQ_OPEN": "{%", + "JQ_CLOSE": "%}", }) diff --git a/course/im.py b/course/im.py index 6b9856e0410065b528ca8cf147f5af9949a8e772..da47f7cae7c0ffefb216f5ca6055d9ab47b6ef6e 100644 --- a/course/im.py +++ b/course/im.py @@ -107,7 +107,7 @@ class CourseXMPP(sleekxmpp.ClientXMPP): """ Track how many roster entries have received presence updates. """ - self.received.add(pres['from'].bare) + self.received.add(pres["from"].bare) if len(self.received) >= len(self.client_roster.keys()): self.presences_received.set() else: @@ -196,7 +196,7 @@ def send_instant_message(pctx): xmpp.send_message( mto=course.recipient_xmpp_id, mbody=form.cleaned_data["message"], - mtype='chat') + mtype="chat") except Exception: from traceback import print_exc diff --git a/course/management/commands/test.py b/course/management/commands/test.py index 2e2d9e15bcea85beabf8d6f6549dd624155f3abc..e26ccbac302a98f7f850966a7a385d7dbeb84adf 100644 --- a/course/management/commands/test.py +++ b/course/management/commands/test.py @@ -5,15 +5,15 @@ class Command(DjangoTestCommand): def add_arguments(self, parser): super(Command, self).add_arguments(parser) parser.add_argument( - '--local_test_settings', action='store', - dest='local_test_settings', - help=('Overrides the default local test setting file path. ' - 'The default value is "local_settings_example.py" in ' - 'project root. Note that local settings for production ' + "--local_test_settings", action="store", + dest="local_test_settings", + help=("Overrides the default local test setting file path. " + "The default value is 'local_settings_example.py' in " + "project root. Note that local settings for production " '("local_settings.py") is not allowed to be used ' - 'for unit tests for security reason.') + "for unit tests for security reason.") ) def handle(self, *test_labels, **options): - del options['local_test_settings'] + del options["local_test_settings"] super(Command, self).handle(*test_labels, **options) diff --git a/course/models.py b/course/models.py index 722352685dfd7f20ffde637c7e75ecfe3a72dcf3..d8b386dec9d0650aa1b874089afa982d2880bc08 100644 --- a/course/models.py +++ b/course/models.py @@ -91,7 +91,7 @@ class Course(models.Model): "on your file system where the course's git repository lives. " "This should not be changed after the course has been created " "without also moving the course's git on the server."), - verbose_name=_('Course identifier'), + verbose_name=_("Course identifier"), db_index=True, validators=[ RegexValidator( @@ -104,110 +104,110 @@ class Course(models.Model): name = models.CharField( null=True, blank=False, max_length=200, - verbose_name=_('Course name'), + verbose_name=_("Course name"), help_text=_("A human-readable name for the course. " "(e.g. 'Numerical Methods')")) number = models.CharField( null=True, blank=False, max_length=200, - verbose_name=_('Course number'), + verbose_name=_("Course number"), help_text=_("A human-readable course number/ID " "for the course (e.g. 'CS123')")) time_period = models.CharField( null=True, blank=False, max_length=200, - verbose_name=_('Time Period'), + verbose_name=_("Time Period"), help_text=_("A human-readable description of the " "time period for the course (e.g. 'Fall 2014')")) start_date = models.DateField( - verbose_name=_('Start date'), + verbose_name=_("Start date"), null=True, blank=True) end_date = models.DateField( - verbose_name=_('End date'), + verbose_name=_("End date"), null=True, blank=True) hidden = models.BooleanField( default=True, help_text=_("Is the course only accessible to course staff?"), - verbose_name=_('Only visible to course staff')) + verbose_name=_("Only visible to course staff")) listed = models.BooleanField( default=True, help_text=_("Should the course be listed on the main page?"), - verbose_name=_('Listed on main page')) + verbose_name=_("Listed on main page")) accepts_enrollment = models.BooleanField( default=True, - verbose_name=_('Accepts enrollment')) + verbose_name=_("Accepts enrollment")) git_source = models.CharField(max_length=200, blank=False, 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."), - verbose_name=_('git source')) + 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." "You may use this tool to generate " "a key pair."), - verbose_name=_('SSH private key')) + verbose_name=_("SSH private key")) course_root_path = models.CharField(max_length=200, blank=True, help_text=_( - 'Subdirectory within the git repository to use as ' - 'course root directory. Not required, and usually blank. ' - 'Use only if your course content lives in a subdirectory ' - 'of your git repository. ' - 'Should not include trailing slash.'), - verbose_name=_('Course root in repository')) + "Subdirectory within the git repository to use as " + "course root directory. Not required, and usually blank. " + "Use only if your course content lives in a subdirectory " + "of your git repository. " + "Should not include trailing slash."), + verbose_name=_("Course root in repository")) 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."), - verbose_name=_('Course file')) + 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."), - verbose_name=_('Events file')) + verbose_name=_("Events file")) enrollment_approval_required = models.BooleanField( default=False, help_text=_("If set, each enrolling student must be " "individually approved."), - verbose_name=_('Enrollment approval required')) + verbose_name=_("Enrollment approval required")) preapproval_require_verified_inst_id = models.BooleanField( default=True, help_text=_("If set, students cannot get participation " "preapproval using institutional ID if " "the institutional ID they provided is not " "verified."), - verbose_name=_('Prevent preapproval by institutional ID if not ' - 'verified?')) + verbose_name=_("Prevent preapproval by institutional ID if not " + "verified?")) 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'."), - verbose_name=_('Enrollment required email suffix')) + verbose_name=_("Enrollment required email suffix")) from_email = models.EmailField( # Translators: replace "RELATE" with the brand name of your # website if necessary. help_text=_("This email address will be used in the 'From' line " "of automated emails sent by RELATE."), - verbose_name=_('From email')) + verbose_name=_("From email")) notify_email = models.EmailField( help_text=_("This email address will receive " "notifications about the course."), - verbose_name=_('Notify email')) + verbose_name=_("Notify email")) force_lang = models.CharField(max_length=200, blank=True, null=True, default="", validators=[validate_course_specific_language], help_text=_( "Which language is forced to be used for this course."), - verbose_name=_('Course language forcibly used')) + verbose_name=_("Course language forcibly used")) # {{{ XMPP @@ -215,25 +215,25 @@ class Course(models.Model): 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."), - verbose_name=_('Course xmpp ID')) + 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."), - verbose_name=_('Course xmpp password')) + 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."), - verbose_name=_('Recipient xmpp ID')) + verbose_name=_("Recipient xmpp ID")) # }}} active_git_commit_sha = models.CharField(max_length=200, null=False, blank=False, - verbose_name=_('Active git commit SHA')) + verbose_name=_("Active git commit SHA")) participants = models.ManyToManyField(settings.AUTH_USER_MODEL, - through='Participation') + through="Participation") class Meta: verbose_name = _("Course") @@ -282,12 +282,12 @@ class Event(models.Model): """ course = models.ForeignKey(Course, - verbose_name=_('Course'), on_delete=models.CASCADE) + verbose_name=_("Course"), on_delete=models.CASCADE) kind = models.CharField(max_length=50, # Translators: format of event kind in Event model help_text=_("Should be lower_case_with_underscores, no spaces " "allowed."), - verbose_name=_('Kind of event'), + verbose_name=_("Kind of event"), validators=[ RegexValidator( "^" + EVENT_KIND_REGEX + "$", @@ -297,20 +297,20 @@ class Event(models.Model): ) ordinal = models.IntegerField(blank=True, null=True, # Translators: ordinal of event of the same kind - verbose_name=_('Ordinal of event')) + verbose_name=_("Ordinal of event")) - time = models.DateTimeField(verbose_name=_('Start time')) + time = models.DateTimeField(verbose_name=_("Start time")) end_time = models.DateTimeField(null=True, blank=True, - verbose_name=_('End time')) + 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"), - verbose_name=_('All day')) + verbose_name=_("All day")) shown_in_calendar = models.BooleanField(default=True, - verbose_name=_('Shown in calendar')) + verbose_name=_("Shown in calendar")) class Meta: verbose_name = _("Event") @@ -363,14 +363,14 @@ class Event(models.Model): class ParticipationTag(models.Model): course = models.ForeignKey(Course, - verbose_name=_('Course'), on_delete=models.CASCADE) + verbose_name=_("Course"), on_delete=models.CASCADE) name = models.CharField(max_length=100, # Translators: name format of ParticipationTag help_text=_("Format is lower-case-with-hyphens. " "Do not use spaces."), - verbose_name=_('Name of participation tag')) + verbose_name=_("Name of participation tag")) shown_to_participant = models.BooleanField(default=False, - verbose_name=_('Shown to pariticpant')) + verbose_name=_("Shown to pariticpant")) def clean(self): super(ParticipationTag, self).clean() @@ -398,22 +398,22 @@ class ParticipationTag(models.Model): class ParticipationRole(models.Model): course = models.ForeignKey(Course, - verbose_name=_('Course'), on_delete=models.CASCADE) + verbose_name=_("Course"), on_delete=models.CASCADE) identifier = models.CharField( max_length=100, blank=False, null=False, help_text=_("A symbolic name for this role, used in course code. " "lower_case_with_underscores, no spaces. May be any string. The " "name 'unenrolled' is special and refers to anyone not enrolled " "in the course."), - verbose_name=_('Role identifier')) + verbose_name=_("Role identifier")) name = models.CharField(max_length=200, blank=False, null=False, help_text=_("A human-readable description of this role."), - verbose_name=_('Role name')) + verbose_name=_("Role name")) is_default_for_new_participants = models.BooleanField(default=False, - verbose_name=_('Is default role for new participants')) + verbose_name=_("Is default role for new participants")) is_default_for_unenrolled = models.BooleanField(default=False, - verbose_name=_('Is default role for unenrolled users')) + verbose_name=_("Is default role for unenrolled users")) def clean(self): super(ParticipationRole, self).clean() @@ -470,10 +470,10 @@ class ParticipationRole(models.Model): class ParticipationPermissionBase(models.Model): permission = models.CharField(max_length=200, blank=False, null=False, choices=PARTICIPATION_PERMISSION_CHOICES, - verbose_name=_('Permission'), + verbose_name=_("Permission"), db_index=True) argument = models.CharField(max_length=200, blank=True, null=True, - verbose_name=_('Argument')) + verbose_name=_("Argument")) class Meta: abstract = True @@ -489,7 +489,7 @@ class ParticipationPermissionBase(models.Model): class ParticipationRolePermission(ParticipationPermissionBase): role = models.ForeignKey(ParticipationRole, - verbose_name=_('Role'), on_delete=models.CASCADE, + verbose_name=_("Role"), on_delete=models.CASCADE, related_name="permissions") def __unicode__(self): @@ -508,13 +508,13 @@ class ParticipationRolePermission(ParticipationPermissionBase): class Participation(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, - verbose_name=_('User ID'), on_delete=models.CASCADE, + verbose_name=_("User ID"), on_delete=models.CASCADE, related_name="participations") course = models.ForeignKey(Course, related_name="participations", - verbose_name=_('Course'), on_delete=models.CASCADE) + verbose_name=_("Course"), on_delete=models.CASCADE) enroll_time = models.DateTimeField(default=now, - verbose_name=_('Enroll time')) + verbose_name=_("Enroll time")) role = models.CharField(max_length=50, verbose_name=_("Role (unused)"),) roles = models.ManyToManyField(ParticipationRole, blank=True, @@ -522,23 +522,23 @@ class Participation(models.Model): status = models.CharField(max_length=50, choices=PARTICIPATION_STATUS_CHOICES, - verbose_name=_('Participation status')) + verbose_name=_("Participation status")) time_factor = models.DecimalField( max_digits=10, decimal_places=2, default=1, help_text=_("Multiplier for time available on time-limited " "flows"), - verbose_name=_('Time factor')) + verbose_name=_("Time factor")) preview_git_commit_sha = models.CharField(max_length=200, null=True, blank=True, - verbose_name=_('Preview git commit SHA')) + verbose_name=_("Preview git commit SHA")) tags = models.ManyToManyField(ParticipationTag, blank=True, - verbose_name=_('Tags')) + verbose_name=_("Tags")) notes = models.TextField(blank=True, null=True, - verbose_name=_('Notes')) + verbose_name=_("Notes")) def __unicode__(self): # Translators: displayed format of Participation: some user in some @@ -598,7 +598,7 @@ class Participation(models.Model): class ParticipationPermission(ParticipationPermissionBase): participation = models.ForeignKey(Participation, - verbose_name=_('Participation'), on_delete=models.CASCADE, + verbose_name=_("Participation"), on_delete=models.CASCADE, related_name="individual_permissions") class Meta: @@ -609,20 +609,20 @@ class ParticipationPermission(ParticipationPermissionBase): class ParticipationPreapproval(models.Model): email = models.EmailField(max_length=254, null=True, blank=True, - verbose_name=_('Email')) + verbose_name=_("Email")) institutional_id = models.CharField(max_length=254, null=True, blank=True, - verbose_name=_('Institutional ID')) + verbose_name=_("Institutional ID")) course = models.ForeignKey(Course, - verbose_name=_('Course'), on_delete=models.CASCADE) + verbose_name=_("Course"), on_delete=models.CASCADE) role = models.CharField(max_length=50, verbose_name=_("Role (unused)"),) roles = models.ManyToManyField(ParticipationRole, blank=True, verbose_name=_("Roles"), related_name="+") creator = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, - verbose_name=_('Creator'), on_delete=models.SET_NULL) + verbose_name=_("Creator"), on_delete=models.SET_NULL) creation_time = models.DateTimeField(default=now, db_index=True, - verbose_name=_('Creation time')) + verbose_name=_("Creation time")) def __unicode__(self): if self.email: @@ -780,35 +780,35 @@ def _set_up_course_permissions(sender, instance, created, raw, using, update_fie class AuthenticationToken(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, - verbose_name=_('User ID'), on_delete=models.CASCADE) + verbose_name=_("User ID"), on_delete=models.CASCADE) participation = models.ForeignKey(Participation, - verbose_name=_('Participation'), on_delete=models.CASCADE) + verbose_name=_("Participation"), on_delete=models.CASCADE) restrict_to_participation_role = models.ForeignKey(ParticipationRole, - verbose_name=_('Restrict to role'), on_delete=models.CASCADE, + verbose_name=_("Restrict to role"), on_delete=models.CASCADE, blank=True, null=True) description = models.CharField(max_length=200, - verbose_name=_('Description')) + verbose_name=_("Description")) creation_time = models.DateTimeField( - default=now, verbose_name=_('Creation time')) + default=now, verbose_name=_("Creation time")) last_use_time = models.DateTimeField( - verbose_name=_('Last use time'), + verbose_name=_("Last use time"), blank=True, null=True) valid_until = models.DateTimeField( - default=None, verbose_name=_('Valid until'), + default=None, verbose_name=_("Valid until"), blank=True, null=True) revocation_time = models.DateTimeField( - default=None, verbose_name=_('Revocation time'), + default=None, verbose_name=_("Revocation time"), blank=True, null=True) token_hash = models.CharField(max_length=200, help_text=_("A hash of the authentication token to be " "used for direct git access."), null=True, blank=True, unique=True, - verbose_name=_('Hash of git authentication token')) + verbose_name=_("Hash of git authentication token")) def __unicode__(self): return _("Token %(id)d for %(participation)s: %(description)s") % { @@ -830,15 +830,15 @@ class AuthenticationToken(models.Model): class InstantFlowRequest(models.Model): course = models.ForeignKey(Course, - verbose_name=_('Course'), on_delete=models.CASCADE) + verbose_name=_("Course"), on_delete=models.CASCADE) flow_id = models.CharField(max_length=200, - verbose_name=_('Flow ID')) + verbose_name=_("Flow ID")) start_time = models.DateTimeField(default=now, - verbose_name=_('Start time')) + verbose_name=_("Start time")) end_time = models.DateTimeField( - verbose_name=_('End time')) + verbose_name=_("End time")) cancelled = models.BooleanField(default=False, - verbose_name=_('Cancelled')) + verbose_name=_("Cancelled")) class Meta: verbose_name = _("Instant flow request") @@ -864,28 +864,28 @@ class FlowSession(models.Model): # This looks like it's redundant with 'participation', below--but it's not. # 'participation' is nullable. course = models.ForeignKey(Course, - verbose_name=_('Course'), on_delete=models.CASCADE) + verbose_name=_("Course"), on_delete=models.CASCADE) participation = models.ForeignKey(Participation, null=True, blank=True, db_index=True, related_name="flow_sessions", - verbose_name=_('Participation'), on_delete=models.CASCADE) + verbose_name=_("Participation"), on_delete=models.CASCADE) # This looks like it's redundant with participation, above--but it's not. # Again--'participation' is nullable, and it is useful to be able to # remember what user a session belongs to, even if they're not enrolled. user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, - verbose_name=_('User'), on_delete=models.SET_NULL) + verbose_name=_("User"), on_delete=models.SET_NULL) active_git_commit_sha = models.CharField(max_length=200, - verbose_name=_('Active git commit SHA')) + verbose_name=_("Active git commit SHA")) flow_id = models.CharField(max_length=200, db_index=True, - verbose_name=_('Flow ID')) + verbose_name=_("Flow ID")) start_time = models.DateTimeField(default=now, - verbose_name=_('Start time')) + verbose_name=_("Start time")) completion_time = models.DateTimeField(null=True, blank=True, - verbose_name=_('Completion time')) + verbose_name=_("Completion time")) page_count = models.IntegerField(null=True, blank=True, - verbose_name=_('Page count')) + verbose_name=_("Page count")) # This field allows avoiding redundant checks for whether the # page data is in line with the course material and the current @@ -893,20 +893,20 @@ class FlowSession(models.Model): # See course.flow.adjust_flow_session_page_data. page_data_at_revision_key = models.CharField( max_length=200, null=True, blank=True, - verbose_name=_('Page data at course revision'), + verbose_name=_("Page data at course revision"), help_text=_( "Page set-up data is up-to date for this revision of the " "course material")) in_progress = models.BooleanField(default=None, - verbose_name=_('In progress')) + verbose_name=_("In progress")) access_rules_tag = models.CharField(max_length=200, null=True, blank=True, - verbose_name=_('Access rules tag')) + 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, - verbose_name=_('Expiration mode')) + verbose_name=_("Expiration mode")) # Non-normal: These fields can be recomputed, albeit at great expense. # @@ -915,12 +915,12 @@ class FlowSession(models.Model): points = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True, - verbose_name=_('Points')) + verbose_name=_("Points")) max_points = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True, - verbose_name=_('Max point')) + verbose_name=_("Max point")) result_comment = models.TextField(blank=True, null=True, - verbose_name=_('Result comment')) + verbose_name=_("Result comment")) class Meta: verbose_name = _("Flow session") @@ -930,13 +930,13 @@ class FlowSession(models.Model): def __unicode__(self): if self.participation is None: return _("anonymous session %(session_id)d on '%(flow_id)s'") % { - 'session_id': self.id, - 'flow_id': self.flow_id} + "session_id": self.id, + "flow_id": self.flow_id} else: 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} + "user": self.participation.user, + "session_id": self.id, + "flow_id": self.flow_id} __str__ = __unicode__ @@ -987,32 +987,32 @@ class FlowSession(models.Model): class FlowPageData(models.Model): flow_session = models.ForeignKey(FlowSession, related_name="page_data", - verbose_name=_('Flow session'), on_delete=models.CASCADE) + verbose_name=_("Flow session"), on_delete=models.CASCADE) page_ordinal = models.IntegerField(null=True, blank=True, - verbose_name=_('Page ordinal')) + verbose_name=_("Page ordinal")) # This exists to catch changing page types in course content, # which will generally lead to an inconsistency disaster. page_type = models.CharField(max_length=200, - verbose_name=_('Page type as indicated in course content'), + verbose_name=_("Page type as indicated in course content"), null=True, blank=True) group_id = models.CharField(max_length=200, - verbose_name=_('Group ID')) + verbose_name=_("Group ID")) page_id = models.CharField(max_length=200, - verbose_name=_('Page ID')) + verbose_name=_("Page ID")) data = JSONField(null=True, blank=True, # Show correct characters in admin for non ascii languages. - dump_kwargs={'ensure_ascii': False}, - verbose_name=_('Data')) + dump_kwargs={"ensure_ascii": False}, + verbose_name=_("Data")) title = models.CharField(max_length=1000, - verbose_name=_('Page Title'), null=True, blank=True) + verbose_name=_("Page Title"), null=True, blank=True) bookmarked = models.BooleanField(default=False, help_text=_("A user-facing 'marking' feature to allow participants to " "easily return to pages that still need their attention."), - verbose_name=_('Bookmarked')) + verbose_name=_("Bookmarked")) class Meta: verbose_name = _("Flow page data") @@ -1022,10 +1022,10 @@ class FlowPageData(models.Model): # flow page data return (_("Data for page '%(group_id)s/%(page_id)s' " "(page ordinal %(page_ordinal)s) in %(flow_session)s") % { - 'group_id': self.group_id, - 'page_id': self.page_id, - 'page_ordinal': self.page_ordinal, - 'flow_session': self.flow_session}) + "group_id": self.group_id, + "page_id": self.page_id, + "page_ordinal": self.page_ordinal, + "flow_session": self.flow_session}) __str__ = __unicode__ @@ -1049,34 +1049,34 @@ class FlowPageVisit(models.Model): # page_data), but it helps the admin site understand the link # and provide editing. flow_session = models.ForeignKey(FlowSession, db_index=True, - verbose_name=_('Flow session'), on_delete=models.CASCADE) + verbose_name=_("Flow session"), on_delete=models.CASCADE) page_data = models.ForeignKey(FlowPageData, db_index=True, - verbose_name=_('Page data'), on_delete=models.CASCADE) + verbose_name=_("Page data"), on_delete=models.CASCADE) visit_time = models.DateTimeField(default=now, db_index=True, - verbose_name=_('Visit time')) + verbose_name=_("Visit time")) remote_address = models.GenericIPAddressField(null=True, blank=True, - verbose_name=_('Remote address')) + verbose_name=_("Remote address")) user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, related_name="visitor", - verbose_name=_('User'), on_delete=models.SET_NULL) + verbose_name=_("User"), on_delete=models.SET_NULL) impersonated_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, related_name="impersonator", - verbose_name=_('Impersonated by'), on_delete=models.SET_NULL) + verbose_name=_("Impersonated by"), on_delete=models.SET_NULL) is_synthetic = models.BooleanField(default=False, help_text=_("Synthetic flow page visits are generated for " "unvisited pages once a flow is finished. This is needed " "since grade information is attached to a visit, and it " "needs a place to go."), - verbose_name=_('Is synthetic')) + verbose_name=_("Is synthetic")) answer = JSONField(null=True, blank=True, # Show correct characters in admin for non ascii languages. - dump_kwargs={'ensure_ascii': False}, + dump_kwargs={"ensure_ascii": False}, # Translators: "Answer" is a Noun. - verbose_name=_('Answer')) + verbose_name=_("Answer")) # is_submitted_answer may seem redundant with answers being # non-NULL, but it isn't. This supports saved (but as @@ -1089,7 +1089,7 @@ class FlowPageVisit(models.Model): is_submitted_answer = models.NullBooleanField( # Translators: determine whether the answer is a final, # submitted answer fit for grading. - verbose_name=_('Is submitted answer')) + verbose_name=_("Is submitted answer")) def __unicode__(self): result = ( @@ -1147,22 +1147,22 @@ class FlowPageVisit(models.Model): class FlowPageVisitGrade(models.Model): visit = models.ForeignKey(FlowPageVisit, related_name="grades", - verbose_name=_('Visit'), on_delete=models.CASCADE) + verbose_name=_("Visit"), on_delete=models.CASCADE) - # NULL means 'autograded' + # NULL means "autograded" grader = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, - verbose_name=_('Grader'), on_delete=models.SET_NULL) + verbose_name=_("Grader"), on_delete=models.SET_NULL) grade_time = models.DateTimeField(db_index=True, default=now, - verbose_name=_('Grade time')) + verbose_name=_("Grade time")) graded_at_git_commit_sha = models.CharField( max_length=200, null=True, blank=True, - verbose_name=_('Graded at git commit SHA')) + verbose_name=_("Graded at git commit SHA")) 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')) + 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 @@ -1172,12 +1172,12 @@ class FlowPageVisitGrade(models.Model): # Translators: max point in grade help_text=_("Point value of this question when receiving " "full credit."), - verbose_name=_('Max points')) + 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."), - verbose_name=_('Correctness')) + verbose_name=_("Correctness")) # This JSON object has fields corresponding to # :class:`course.page.AnswerFeedback`, except for @@ -1186,9 +1186,9 @@ class FlowPageVisitGrade(models.Model): feedback = JSONField(null=True, blank=True, # Show correct characters in admin for non ascii languages. - dump_kwargs={'ensure_ascii': False}, + dump_kwargs={"ensure_ascii": False}, # Translators: "Feedback" stands for the feedback of answers. - verbose_name=_('Feedback')) + verbose_name=_("Feedback")) def percentage(self): if self.correctness is not None: @@ -1224,14 +1224,14 @@ 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, - verbose_name=_('Page data'), on_delete=models.CASCADE) + verbose_name=_("Page data"), on_delete=models.CASCADE) grade = models.ForeignKey(FlowPageVisitGrade, - verbose_name=_('Grade'), on_delete=models.CASCADE) + verbose_name=_("Grade"), on_delete=models.CASCADE) 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')) + dump_kwargs={"ensure_ascii": False}, + verbose_name=_("Bulk feedback")) def update_bulk_feedback(page_data, grade, bulk_feedback_json): @@ -1293,11 +1293,11 @@ class FlowAccessException(models.Model): # pragma: no cover (deprecated and not # deprecated participation = models.ForeignKey(Participation, db_index=True, - verbose_name=_('Participation'), on_delete=models.CASCADE) + verbose_name=_("Participation"), on_delete=models.CASCADE) flow_id = models.CharField(max_length=200, blank=False, null=False, - verbose_name=_('Flow ID')) + verbose_name=_("Flow ID")) expiration = models.DateTimeField(blank=True, null=True, - verbose_name=_('Expiration')) + verbose_name=_("Expiration")) stipulations = JSONField(blank=True, null=True, # Translators: help text for stipulations in FlowAccessException @@ -1307,13 +1307,13 @@ class FlowAccessException(models.Model): # pragma: no cover (deprecated and not "credit_percent. If not specified here, values will default " "to the stipulations in the course content."), validators=[validate_stipulations], - dump_kwargs={'ensure_ascii': False}, - verbose_name=_('Stipulations')) + dump_kwargs={"ensure_ascii": False}, + verbose_name=_("Stipulations")) creator = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, - verbose_name=_('Creator'), on_delete=models.SET_NULL) + verbose_name=_("Creator"), on_delete=models.SET_NULL) creation_time = models.DateTimeField(default=now, db_index=True, - verbose_name=_('Creation time')) + verbose_name=_("Creation time")) is_sticky = models.BooleanField( default=False, @@ -1322,10 +1322,10 @@ class FlowAccessException(models.Model): # pragma: no cover (deprecated and not "exception rule set should stay " "under this rule set until it is expired."), # Translators: deprecated - verbose_name=_('Is sticky')) + verbose_name=_("Is sticky")) comment = models.TextField(blank=True, null=True, - verbose_name=_('Comment')) + verbose_name=_("Comment")) def __unicode__(self): return ( @@ -1346,10 +1346,10 @@ class FlowAccessExceptionEntry(models.Model): # pragma: no cover (deprecated an exception = models.ForeignKey(FlowAccessException, related_name="entries", - verbose_name=_('Exception'), on_delete=models.CASCADE) + verbose_name=_("Exception"), on_delete=models.CASCADE) permission = models.CharField(max_length=50, choices=FLOW_PERMISSION_CHOICES, - verbose_name=_('Permission')) + verbose_name=_("Permission")) class Meta: # Translators: FlowAccessExceptionEntry (deprecated) @@ -1365,25 +1365,25 @@ class FlowAccessExceptionEntry(models.Model): # pragma: no cover (deprecated an class FlowRuleException(models.Model): flow_id = models.CharField(max_length=200, blank=False, null=False, - verbose_name=_('Flow ID')) + verbose_name=_("Flow ID")) participation = models.ForeignKey(Participation, db_index=True, - verbose_name=_('Participation'), on_delete=models.CASCADE) + verbose_name=_("Participation"), on_delete=models.CASCADE) expiration = models.DateTimeField(blank=True, null=True, - verbose_name=_('Expiration')) + verbose_name=_("Expiration")) creator = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, - verbose_name=_('Creator'), on_delete=models.SET_NULL) + verbose_name=_("Creator"), on_delete=models.SET_NULL) creation_time = models.DateTimeField(default=now, db_index=True, - verbose_name=_('Creation time')) + verbose_name=_("Creation time")) comment = models.TextField(blank=True, null=True, - verbose_name=_('Comment')) + verbose_name=_("Comment")) kind = models.CharField(max_length=50, blank=False, null=False, choices=FLOW_RULE_KIND_CHOICES, - verbose_name=_('Kind')) + verbose_name=_("Kind")) rule = YAMLField(blank=False, null=False, - verbose_name=_('Rule')) + verbose_name=_("Rule")) active = models.BooleanField(default=True, verbose_name=pgettext_lazy( "Is the flow rule exception activated?", "Active")) @@ -1474,13 +1474,13 @@ class FlowRuleException(models.Model): class GradingOpportunity(models.Model): course = models.ForeignKey(Course, - verbose_name=_('Course'), on_delete=models.CASCADE) + verbose_name=_("Course"), on_delete=models.CASCADE) 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."), - verbose_name=_('Grading opportunity ID'), + verbose_name=_("Grading opportunity ID"), validators=[ RegexValidator( "^"+GRADING_OPP_ID_REGEX+"$", @@ -1491,25 +1491,25 @@ class GradingOpportunity(models.Model): name = models.CharField(max_length=200, blank=False, null=False, # Translators: name for GradingOpportunity help_text=_("A human-readable identifier for the grade."), - verbose_name=_('Grading opportunity name')) + 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"), - verbose_name=_('Flow ID')) + verbose_name=_("Flow ID")) aggregation_strategy = models.CharField(max_length=20, choices=GRADE_AGGREGATION_STRATEGY_CHOICES, # Translators: strategy on how the grading of mutiple sessioins # are aggregated. - verbose_name=_('Aggregation strategy')) + verbose_name=_("Aggregation strategy")) due_time = models.DateTimeField(default=None, blank=True, null=True, - verbose_name=_('Due time')) + verbose_name=_("Due time")) creation_time = models.DateTimeField(default=now, - verbose_name=_('Creation time')) + verbose_name=_("Creation time")) shown_in_grade_book = models.BooleanField(default=True, - verbose_name=_('Shown in grade book')) + verbose_name=_("Shown in grade book")) shown_in_participant_grade_book = models.BooleanField(default=True, verbose_name=_("Shown in student grade book")) result_shown_in_participant_grade_book = models.BooleanField(default=True, @@ -1519,16 +1519,16 @@ class GradingOpportunity(models.Model): verbose_name=_("Scores for individual pages are shown " "in the participants' grade book")) hide_superseded_grade_history_before = models.DateTimeField( - verbose_name=_('Hide superseded grade history before'), + verbose_name=_("Hide superseded grade history before"), blank=True, null=True, help_text=_( - 'Grade changes dated before this date that are ' - 'superseded by later grade changes will not be shown to ' - 'participants. ' - 'This can help avoid discussions about pre-release grading ' - 'adjustments. ' - 'May be blank. In that case, the entire grade history is ' - 'shown.')) + "Grade changes dated before this date that are " + "superseded by later grade changes will not be shown to " + "participants. " + "This can help avoid discussions about pre-release grading " + "adjustments. " + "May be blank. In that case, the entire grade history is " + "shown.")) class Meta: verbose_name = _("Grading opportunity") @@ -1561,15 +1561,15 @@ class GradeChange(models.Model): ones. """ opportunity = models.ForeignKey(GradingOpportunity, - verbose_name=_('Grading opportunity'), on_delete=models.CASCADE) + verbose_name=_("Grading opportunity"), on_delete=models.CASCADE) participation = models.ForeignKey(Participation, - verbose_name=_('Participation'), on_delete=models.CASCADE) + verbose_name=_("Participation"), on_delete=models.CASCADE) state = models.CharField(max_length=50, choices=GRADE_STATE_CHANGE_CHOICES, # Translators: something like 'status'. - verbose_name=_('State')) + verbose_name=_("State")) attempt_id = models.CharField(max_length=50, null=True, blank=True, default="main", @@ -1577,28 +1577,28 @@ class GradeChange(models.Model): help_text=_("Grade changes are grouped by their 'attempt ID' " "where later grades with the same attempt ID supersede earlier " "ones."), - verbose_name=_('Attempt ID')) + verbose_name=_("Attempt ID")) points = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True, - verbose_name=_('Points')) + verbose_name=_("Points")) max_points = models.DecimalField(max_digits=10, decimal_places=2, - verbose_name=_('Max points')) + verbose_name=_("Max points")) comment = models.TextField(blank=True, null=True, - verbose_name=_('Comment')) + verbose_name=_("Comment")) due_time = models.DateTimeField(default=None, blank=True, null=True, - verbose_name=_('Due time')) + verbose_name=_("Due time")) creator = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, - verbose_name=_('Creator'), on_delete=models.SET_NULL) + verbose_name=_("Creator"), on_delete=models.SET_NULL) grade_time = models.DateTimeField(default=now, db_index=True, - verbose_name=_('Grade time')) + verbose_name=_("Grade time")) flow_session = models.ForeignKey(FlowSession, null=True, blank=True, related_name="grade_changes", - verbose_name=_('Flow session'), on_delete=models.SET_NULL) + verbose_name=_("Flow session"), on_delete=models.SET_NULL) class Meta: verbose_name = _("Grade change") @@ -1608,9 +1608,9 @@ class GradeChange(models.Model): def __unicode__(self): # Translators: information for GradeChange return _("%(participation)s %(state)s on %(opportunityname)s") % { - 'participation': self.participation, - 'state': self.state, - 'opportunityname': self.opportunity.name} + "participation": self.participation, + "state": self.state, + "opportunityname": self.opportunity.name} __str__ = __unicode__ @@ -1852,11 +1852,11 @@ def get_flow_grading_opportunity(course, flow_id, flow_desc, class InstantMessage(models.Model): participation = models.ForeignKey(Participation, - verbose_name=_('Participation'), on_delete=models.CASCADE) + verbose_name=_("Participation"), on_delete=models.CASCADE) text = models.CharField(max_length=200, - verbose_name=_('Text')) + verbose_name=_("Text")) time = models.DateTimeField(default=now, - verbose_name=_('Time')) + verbose_name=_("Time")) class Meta: verbose_name = _("Instant message") @@ -1875,27 +1875,27 @@ class InstantMessage(models.Model): class Exam(models.Model): course = models.ForeignKey(Course, - verbose_name=_('Course'), on_delete=models.CASCADE) + verbose_name=_("Course"), on_delete=models.CASCADE) description = models.CharField(max_length=200, - verbose_name=_('Description')) + verbose_name=_("Description")) flow_id = models.CharField(max_length=200, - verbose_name=_('Flow ID')) + verbose_name=_("Flow ID")) active = models.BooleanField( default=True, verbose_name=_("Active"), help_text=_( - 'Currently active, i.e. may be used to log in ' - 'via an exam ticket')) + "Currently active, i.e. may be used to log in " + "via an exam ticket")) listed = models.BooleanField( verbose_name=_("Listed"), default=True, - help_text=_('Shown in the list of current exams')) + help_text=_("Shown in the list of current exams")) no_exams_before = models.DateTimeField( - verbose_name=_('No exams before')) + verbose_name=_("No exams before")) no_exams_after = models.DateTimeField( null=True, blank=True, - verbose_name=_('No exams after')) + verbose_name=_("No exams after")) class Meta: verbose_name = _("Exam") @@ -1904,8 +1904,8 @@ class Exam(models.Model): def __unicode__(self): return _("Exam %(description)s in %(course)s") % { - 'description': self.description, - 'course': self.course, + "description": self.description, + "course": self.course, } __str__ = __unicode__ @@ -1913,35 +1913,35 @@ class Exam(models.Model): class ExamTicket(models.Model): exam = models.ForeignKey(Exam, - verbose_name=_('Exam'), on_delete=models.CASCADE) + verbose_name=_("Exam"), on_delete=models.CASCADE) participation = models.ForeignKey(Participation, db_index=True, - verbose_name=_('Participation'), on_delete=models.CASCADE) + verbose_name=_("Participation"), on_delete=models.CASCADE) creator = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, - verbose_name=_('Creator'), on_delete=models.SET_NULL) + verbose_name=_("Creator"), on_delete=models.SET_NULL) creation_time = models.DateTimeField(default=now, - verbose_name=_('Creation time')) + verbose_name=_("Creation time")) usage_time = models.DateTimeField( verbose_name=_("Usage time"), - help_text=_('Date and time of first usage of ticket'), + help_text=_("Date and time of first usage of ticket"), null=True, blank=True) state = models.CharField(max_length=50, choices=EXAM_TICKET_STATE_CHOICES, - verbose_name=_('Exam ticket state')) + verbose_name=_("Exam ticket state")) code = models.CharField(max_length=50, db_index=True, unique=True) valid_start_time = models.DateTimeField( verbose_name=_("End valid period"), - help_text=_('If not blank, date and time at which this exam ticket ' - 'starts being valid/usable'), + help_text=_("If not blank, date and time at which this exam ticket " + "starts being valid/usable"), null=True, blank=True) valid_end_time = models.DateTimeField( verbose_name=_("End valid period"), - help_text=_('If not blank, date and time at which this exam ticket ' - 'stops being valid/usable'), + help_text=_("If not blank, date and time at which this exam ticket " + "stops being valid/usable"), null=True, blank=True) restrict_to_facility = models.CharField(max_length=200, blank=True, null=True, verbose_name=_("Restrict to facility"), @@ -1958,8 +1958,8 @@ class ExamTicket(models.Model): def __unicode__(self): return _("Exam ticket for %(participation)s in %(exam)s") % { - 'participation': self.participation, - 'exam': self.exam, + "participation": self.participation, + "exam": self.exam, } __str__ = __unicode__ diff --git a/course/page/base.py b/course/page/base.py index 3f320f5a1f47489518a88b96d0b1ae585fc2bb53..319d2183f5ecf68e3663348385a4612b81b8b154 100644 --- a/course/page/base.py +++ b/course/page/base.py @@ -1104,8 +1104,8 @@ class PageBaseWithHumanTextFeedback(PageBase): msg = EmailMessage( string_concat("[%(identifier)s:%(flow_id)s] ", _("New notification")) - % {'identifier': page_context.course.identifier, - 'flow_id': page_context.flow_session.flow_id}, + % {"identifier": page_context.course.identifier, + "flow_id": page_context.flow_session.flow_id}, message, getattr(settings, "GRADER_FEEDBACK_EMAIL_FROM", page_context.course.get_from_email()), @@ -1152,9 +1152,9 @@ class PageBaseWithHumanTextFeedback(PageBase): msg = EmailMessage( string_concat("[%(identifier)s:%(flow_id)s] ", _("Grading notes from %(ta)s")) - % {'identifier': page_context.course.identifier, - 'flow_id': page_context.flow_session.flow_id, - 'ta': request.user.get_full_name() + % {"identifier": page_context.course.identifier, + "flow_id": page_context.flow_session.flow_id, + "ta": request.user.get_full_name() }, message, getattr(settings, "GRADER_FEEDBACK_EMAIL_FROM", diff --git a/course/page/choice.py b/course/page/choice.py index 897924b2fec8e9cdda27ee0a2f8ab378830c6cb1..b25fc3d65d1430dffd093cbbfe9a86900ecef41c 100644 --- a/course/page/choice.py +++ b/course/page/choice.py @@ -92,7 +92,7 @@ class ChoiceInfo(object): except Exception: raise ValidationError( _("%(location)s: unable to convert to string") - % {'location': location}) + % {"location": location}) tag_mode_dict = { cls.CORRECT_TAG: ChoiceModes.CORRECT, @@ -118,8 +118,8 @@ class ChoiceInfo(object): raise ValidationError( _("%(location)s: more than one choice modes " "set: '%(modes)s'") - % {'location': location, - 'modes': + % {"location": location, + "modes": "".join([find_tag_by_mode(item_mode[0]), prefix]) }) @@ -310,8 +310,8 @@ class ChoiceQuestion(ChoiceQuestionBase): _("one or more correct answer(s) " "expected, %(n_correct)d found")) % { - 'location': location, - 'n_correct': self.correct_choice_count}) + "location": location, + "n_correct": self.correct_choice_count}) if self.disregard_choice_count: raise ValidationError( @@ -319,7 +319,7 @@ class ChoiceQuestion(ChoiceQuestionBase): "%(location)s: ", _("ChoiceQuestion does not allow any choices " "marked 'disregard'")) - % {'location': location}) + % {"location": location}) if self.always_correct_choice_count: raise ValidationError( @@ -327,7 +327,7 @@ class ChoiceQuestion(ChoiceQuestionBase): "%(location)s: ", _("ChoiceQuestion does not allow any choices " "marked 'always_correct'")) - % {'location': location}) + % {"location": location}) def allowed_attrs(self): return super(ChoiceQuestion, self).allowed_attrs() + ( @@ -351,7 +351,7 @@ class ChoiceQuestion(ChoiceQuestionBase): *args, **kwargs) if not page_behavior.may_change_answer: - form.fields['choice'].widget.attrs['disabled'] = True + form.fields["choice"].widget.attrs["disabled"] = True return form @@ -510,7 +510,7 @@ class MultipleChoiceQuestion(ChoiceQuestionBase): _("'allow_partial_credit' or " "'allow_partial_credit_subset_only' may not be specified" "at the same time as 'credit_mode'")) - % {'location': location}) + % {"location": location}) else: @@ -531,7 +531,7 @@ class MultipleChoiceQuestion(ChoiceQuestionBase): _("'allow_partial_credit' and " "'allow_partial_credit_subset_only' are not allowed to " "coexist when both attribute are 'True'")) - % {'location': location}) + % {"location": location}) if credit_mode not in [ "exact", @@ -541,7 +541,7 @@ class MultipleChoiceQuestion(ChoiceQuestionBase): string_concat( "%(location)s: ", _("unrecognized credit_mode '%(credit_mode)s'")) - % {'location': location, "credit_mode": credit_mode}) + % {"location": location, "credit_mode": credit_mode}) if vctx is not None and not hasattr(pd, "credit_mode"): vctx.add_warning(location, @@ -578,7 +578,7 @@ class MultipleChoiceQuestion(ChoiceQuestionBase): *args, **kwargs) if not page_behavior.may_change_answer: - form.fields['choice'].widget.attrs['disabled'] = True + form.fields["choice"].widget.attrs["disabled"] = True return form @@ -801,7 +801,7 @@ class SurveyChoiceQuestion(PageBaseWithTitle): *args, **kwargs) if not page_behavior.may_change_answer: - form.fields['choice'].widget.attrs['disabled'] = True + form.fields["choice"].widget.attrs["disabled"] = True return form diff --git a/course/page/code.py b/course/page/code.py index 7787640e9aac8cab535af102c1e8728f95d2ae82..19566a3aa45fd80afb6d574f787cf0a6e4b02d58 100644 --- a/course/page/code.py +++ b/course/page/code.py @@ -200,12 +200,12 @@ def request_run(run_req, run_timeout, image=None): def debug_print(s): pass - command_path = '/opt/runcode/runcode' - user = 'runcode' + command_path = "/opt/runcode/runcode" + user = "runcode" # The following is necessary because tests don't arise from a CodeQuestion # object, so we provide a fallback. - debug_print('Image is %s.' % repr(image)) + debug_print("Image is %s." % repr(image)) if image is None: image = settings.RELATE_DOCKER_RUNPY_IMAGE @@ -238,7 +238,7 @@ def request_run(run_req, run_timeout, image=None): else: container_id = None - connect_host_ip = 'localhost' + connect_host_ip = "localhost" try: # FIXME: Prohibit networking @@ -282,7 +282,7 @@ def request_run(run_req, run_timeout, image=None): try: connection = http_client.HTTPConnection(connect_host_ip, port) - connection.request('GET', '/ping') + connection.request("GET", "/ping") response = connection.getresponse() response_data = response.read().decode() @@ -315,7 +315,7 @@ def request_run(run_req, run_timeout, image=None): connection = http_client.HTTPConnection(connect_host_ip, port, timeout=1 + run_timeout) - headers = {'Content-type': 'application/json'} + headers = {"Content-type": "application/json"} json_run_req = json.dumps(run_req).encode("utf-8") @@ -323,7 +323,7 @@ def request_run(run_req, run_timeout, image=None): start_time = time() debug_print("BEFPOST") - connection.request('POST', '/run-python', json_run_req, headers) + connection.request("POST", "/run-python", json_run_req, headers) debug_print("AFTPOST") http_response = connection.getresponse() @@ -1199,7 +1199,7 @@ class PythonCodeQuestion(CodeQuestion): @property def language_mode(self): - return 'python' + return "python" @property def container_image(self): @@ -1207,9 +1207,9 @@ class PythonCodeQuestion(CodeQuestion): @property def suffix(self): - return '.py' + return ".py" - def __init__(self, vctx, location, page_desc, language_mode='python'): + def __init__(self, vctx, location, page_desc, language_mode="python"): super(PythonCodeQuestion, self).__init__(vctx, location, page_desc, language_mode) @@ -1279,7 +1279,7 @@ class PythonCodeQuestionWithHumanTextFeedback( _("'human_feedback_value' and " "'human_feedback_percentage' are not " "allowed to coexist")) - % {'location': location} + % {"location": location} ) if not (hasattr(self.page_desc, "human_feedback_value") or hasattr(self.page_desc, "human_feedback_percentage")): @@ -1288,7 +1288,7 @@ class PythonCodeQuestionWithHumanTextFeedback( "%(location)s: ", _("expecting either 'human_feedback_value' " "or 'human_feedback_percentage', found neither.")) - % {'location': location} + % {"location": location} ) if hasattr(self.page_desc, "human_feedback_value"): vctx.add_warning( diff --git a/course/page/code_run_backend.py b/course/page/code_run_backend.py index fee8718c4e82447c8213723e9f314a81c3dd325a..93f3da39af3d1b30fddc80863fe44f969ca9d7ee 100644 --- a/course/page/code_run_backend.py +++ b/course/page/code_run_backend.py @@ -177,7 +177,7 @@ def run_code(result, run_req): if getattr(run_req, "setup_code", None): try: setup_code = compile( - run_req.setup_code, "[setup code]", 'exec') + run_req.setup_code, "[setup code]", "exec") except Exception: package_exception(result, "setup_compile_error") return @@ -186,7 +186,7 @@ def run_code(result, run_req): try: user_code = compile( - run_req.user_code, "[user code]", 'exec') + run_req.user_code, "[user code]", "exec") except Exception: package_exception(result, "user_compile_error") return @@ -194,7 +194,7 @@ def run_code(result, run_req): if getattr(run_req, "test_code", None): try: test_code = compile( - run_req.test_code, "[test code]", 'exec') + run_req.test_code, "[test code]", "exec") except Exception: package_exception(result, "test_compile_error") return diff --git a/course/page/inline.py b/course/page/inline.py index 9f33ed25a5f5ec48931ccbaf61aa1c4cd372c920..0b2a16de14b95a0d897eaa753be9ca735dcb8e97 100644 --- a/course/page/inline.py +++ b/course/page/inline.py @@ -97,11 +97,11 @@ class InlineMultiQuestionForm(StyledInlineForm): if isinstance(self.fields[field_name].widget, forms.widgets.Select): # This will also disable the option dropdown - self.fields[field_name].widget.attrs['disabled'] \ + self.fields[field_name].widget.attrs["disabled"] \ = "disabled" else: # Then it should be a TextInput widget - self.fields[field_name].widget.attrs['readonly'] \ + self.fields[field_name].widget.attrs["readonly"] \ = "readonly" self.helper.layout.extend([HTML("

")]) @@ -139,8 +139,8 @@ def get_question_class(location, q_type, answers_desc): "%(location)s: ", _("unknown embedded question type '%(type)s'")) % { - 'location': location, - 'type': q_type}) + "location": location, + "type": q_type}) def parse_question(vctx, location, name, answers_desc): @@ -439,9 +439,9 @@ class ChoicesAnswer(AnswerBase): "%(location)s: '%(answer_name)s' ", _("choice %(idx)d: unable to convert to string") ) - % {'location': location, - 'answer_name': self.name, - 'idx': choice_idx+1}) + % {"location": location, + "answer_name": self.name, + "idx": choice_idx+1}) if choice.startswith(self.CORRECT_TAG): correct_choice_count += 1 @@ -458,9 +458,9 @@ class ChoicesAnswer(AnswerBase): " for question '%(question_name)s', " "%(n_correct)d found")) % { - 'location': location, - 'question_name': self.name, - 'n_correct': correct_choice_count}) + "location": location, + "question_name": self.name, + "n_correct": correct_choice_count}) self.hint = getattr(self.answers_desc, "hint", "") self.width = 0 @@ -695,7 +695,7 @@ class InlineMultiQuestion(TextQuestionBase, PageBaseWithValue): "%(location)s: ", _("InlineMultiQuestion requires at least one " "answer field to be defined.")) - % {'location': location}) + % {"location": location}) for answers_name in answers_name_list: if NAME_VALIDATE_RE.match(answers_name) is None: @@ -883,7 +883,7 @@ class InlineMultiQuestion(TextQuestionBase, PageBaseWithValue): # This happens when rendering the form in analytics view. if not request: - context.update({'csrf_token': "None"}) + context.update({"csrf_token": "None"}) return loader.render_to_string( "course/custom-crispy-inline-form.html", diff --git a/course/page/text.py b/course/page/text.py index 341eb0258b3eaef552efcff037d71958f693dccf..ae51b786f4ce19ee882b4a7855b916cefe4372cc 100644 --- a/course/page/text.py +++ b/course/page/text.py @@ -181,7 +181,7 @@ def get_validator_class(location, validator_type): "%(location)s: ", _("unknown validator type"), "'%(type)s'") - % {'location': location, 'type': validator_type}) + % {"location": location, "type": validator_type}) def parse_validator(vctx, location, validator_desc): @@ -340,7 +340,7 @@ class SymbolicExpressionMatcher(TextAnswerMatcher): _("unable to check symbolic expression"), "(%(err_type)s: %(err_str)s)") % { - 'location': location, + "location": location, "err_type": tp.__name__, "err_str": str(e) }) @@ -550,9 +550,9 @@ def get_matcher_class(location, matcher_type, pattern_type): _("%(matcherclassname)s only accepts " "'%(matchertype)s' patterns")) % { - 'location': location, - 'matcherclassname': matcher_class.__name__, - 'matchertype': matcher_class.pattern_type}) + "location": location, + "matcherclassname": matcher_class.__name__, + "matchertype": matcher_class.pattern_type}) return matcher_class @@ -561,8 +561,8 @@ def get_matcher_class(location, matcher_type, pattern_type): "%(location)s: ", _("unknown match type '%(matchertype)s'")) % { - 'location': location, - 'matchertype': matcher_type}) + "location": location, + "matchertype": matcher_type}) def parse_matcher_string(vctx, location, matcher_desc): @@ -665,8 +665,8 @@ class TextQuestionBase(PageBaseWithTitle): _("unrecognized widget type"), "'%(type)s'") % { - 'location': location, - 'type': getattr(page_desc, "widget")}) + "location": location, + "type": getattr(page_desc, "widget")}) def required_attrs(self): return super(TextQuestionBase, self).required_attrs() + ( diff --git a/course/page/upload.py b/course/page/upload.py index a3347275608764813c037d7d2add8a8c7986b038..52ff8ca8a28b195265e03478826a51c7b00cc4d6 100644 --- a/course/page/upload.py +++ b/course/page/upload.py @@ -44,7 +44,7 @@ from crispy_forms.layout import Layout, Field class FileUploadForm(StyledForm): show_save_button = False uploaded_file = forms.FileField(required=True, - label=gettext_lazy('Uploaded file')) + label=gettext_lazy("Uploaded file")) def __init__(self, maximum_megabytes, mime_types, *args, **kwargs): super(FileUploadForm, self).__init__(*args, **kwargs) @@ -66,15 +66,15 @@ class FileUploadForm(StyledForm): Field("uploaded_file", **field_kwargs)) def clean_uploaded_file(self): - uploaded_file = self.cleaned_data['uploaded_file'] + uploaded_file = self.cleaned_data["uploaded_file"] from django.template.defaultfilters import filesizeformat if uploaded_file.size > self.max_file_size: raise forms.ValidationError( _("Please keep file size under %(allowedsize)s. " "Current filesize is %(uploadedsize)s.") - % {'allowedsize': filesizeformat(self.max_file_size), - 'uploadedsize': filesizeformat(uploaded_file.size)}) + % {"allowedsize": filesizeformat(self.max_file_size), + "uploadedsize": filesizeformat(uploaded_file.size)}) if self.mime_types is not None and self.mime_types == ["application/pdf"]: if uploaded_file.read()[:4] != b"%PDF": @@ -165,7 +165,7 @@ class FileUploadQuestion(PageBaseWithTitle, PageBaseWithValue, _("unrecognized mime types"), " '%(presenttype)s'") % { - 'presenttype': ", ".join( + "presenttype": ", ".join( set(page_desc.mime_types) - set(self.ALLOWED_MIME_TYPES))}) @@ -175,7 +175,7 @@ class FileUploadQuestion(PageBaseWithTitle, PageBaseWithValue, location, ": ", _("'maximum_megabytes' expects a positive value, " "got %(value)s instead") - % {'value': str(page_desc.maximum_megabytes)})) + % {"value": str(page_desc.maximum_megabytes)})) if vctx is not None: if not hasattr(page_desc, "value"): diff --git a/course/sandbox.py b/course/sandbox.py index 9499d5d5bb10e4d1c9e2f3f8a9a4cb75346ce520..8f138010166f7f1c2f72243e1bad1a073e920575 100644 --- a/course/sandbox.py +++ b/course/sandbox.py @@ -114,8 +114,8 @@ def view_markup_sandbox(pctx): preview_text = "" def make_form(data=None): - help_text = (gettext("Enter " + help_text = (gettext('Enter ' "RELATE markup.")) return SandboxForm( None, "markdown", request.user.editor_mode, diff --git a/course/tasks.py b/course/tasks.py index 6bc61b0360637bc45349422f818834cb5d89f625..fec34acce6ee9809ed72524f966b27524d3d1bd1 100644 --- a/course/tasks.py +++ b/course/tasks.py @@ -59,8 +59,8 @@ def expire_in_progress_sessions(self, course_id, flow_id, rule_tag, now_datetime count += 1 self.update_state( - state='PROGRESS', - meta={'current': i, 'total': nsessions}) + state="PROGRESS", + meta={"current": i, "total": nsessions}) repo.close() @@ -96,8 +96,8 @@ def finish_in_progress_sessions(self, course_id, flow_id, rule_tag, now_datetime count += 1 self.update_state( - state='PROGRESS', - meta={'current': i, 'total': nsessions}) + state="PROGRESS", + meta={"current": i, "total": nsessions}) repo.close() @@ -127,8 +127,8 @@ def recalculate_ended_sessions(self, course_id, flow_id, rule_tag): count += 1 self.update_state( - state='PROGRESS', - meta={'current': count, 'total': nsessions}) + state="PROGRESS", + meta={"current": count, "total": nsessions}) repo.close() @@ -161,8 +161,8 @@ def regrade_flow_sessions(self, course_id, flow_id, access_rules_tag, inprog_val count += 1 self.update_state( - state='PROGRESS', - meta={'current': count, 'total': nsessions}) + state="PROGRESS", + meta={"current": count, "total": nsessions}) repo.close() diff --git a/course/templatetags/coursetags.py b/course/templatetags/coursetags.py index 641a8d048d1b449d44acff13d1c39a79fa3d6989..1b5ce4e85edae7d18c7048f1d4181b7810682370 100644 --- a/course/templatetags/coursetags.py +++ b/course/templatetags/coursetags.py @@ -38,7 +38,7 @@ class GetCurrentLanguageJsFmtNode(Node): lang_name = ( translation.to_locale(translation.get_language()).replace("_", "-")) context[self.variable] = lang_name - return '' + return "" @register.tag("get_current_js_lang_name") @@ -61,13 +61,13 @@ def do_get_current_js_lang_name(parser, token): # token.split_contents() isn't useful here because this tag doesn't # accept variable as arguments args = token.contents.split() - if len(args) != 3 or args[1] != 'as': + if len(args) != 3 or args[1] != "as": raise TemplateSyntaxError("'get_current_js_lang_name' requires " "'as variable' (got %r)" % args) return GetCurrentLanguageJsFmtNode(args[2]) -@register.filter(name='js_lang_fallback') +@register.filter(name="js_lang_fallback") def js_lang_fallback(lang_name, js_name=None): """ Return the fallback lang name for js files. @@ -90,7 +90,7 @@ def js_lang_fallback(lang_name, js_name=None): # {{{ filter for participation.has_permission() -@register.filter(name='has_permission') +@register.filter(name="has_permission") def has_permission(participation, arg): """ Check if a participation instance has specific permission. @@ -115,7 +115,7 @@ def has_permission(participation, arg): # }}} -@register.filter(name='may_set_fake_time') +@register.filter(name="may_set_fake_time") def may_set_fake_time(user): """ Check if a user may set fake time. @@ -126,7 +126,7 @@ def may_set_fake_time(user): return msf(user) -@register.filter(name='may_set_pretend_facility') +@register.filter(name="may_set_pretend_facility") def may_set_pretend_facility(user): """ Check if a user may set pretend_facility @@ -137,7 +137,7 @@ def may_set_pretend_facility(user): return mspf(user) -@register.filter(name='commit_message_as_html') +@register.filter(name="commit_message_as_html") def commit_message_as_html(commit_sha, repo): from course.versioning import _get_commit_message_as_html return _get_commit_message_as_html(repo, commit_sha) diff --git a/course/utils.py b/course/utils.py index 8d8792a021c096bc0d38f3b1ea8c9454273554f3..9dc14704fb73dcf194642a25966f2a7bc45c5e4a 100644 --- a/course/utils.py +++ b/course/utils.py @@ -982,13 +982,13 @@ def get_codemirror_widget( if interaction_mode == "vim": actual_config["vimMode"] = True - actual_addon_js += ('../keymap/vim',) + actual_addon_js += ("../keymap/vim",) elif interaction_mode == "emacs": actual_config["keyMap"] = "emacs" - actual_addon_js += ('../keymap/emacs',) + actual_addon_js += ("../keymap/emacs",) elif interaction_mode == "sublime": actual_config["keyMap"] = "sublime" - actual_addon_js += ('../keymap/sublime',) + actual_addon_js += ("../keymap/sublime",) # every other interaction mode goes to default if config is not None: @@ -1042,7 +1042,7 @@ class FacilityFindingMiddleware(object): else: import ipaddress remote_address = ipaddress.ip_address( - str(request.META['REMOTE_ADDR'])) + str(request.META["REMOTE_ADDR"])) facilities = set() @@ -1292,7 +1292,7 @@ class IpynbJinjaMacro(RelateJinjaMacroBase): if clear_markdown: nb_source_dict.update( {"cells": [cell for cell in nb_source_dict["cells"] - if cell['cell_type'] != "markdown"]}) + if cell["cell_type"] != "markdown"]}) nb_source_dict.update({"cells": nb_source_dict["cells"]}) @@ -1344,7 +1344,7 @@ class NBConvertHTMLPostprocessor(markdown.postprocessors.Postprocessor): class NBConvertExtension(markdown.Extension): def extendMarkdown(self, md, md_globals): # noqa - md.postprocessors['relate_nbconvert'] = NBConvertHTMLPostprocessor(md) + md.postprocessors["relate_nbconvert"] = NBConvertHTMLPostprocessor(md) # }}} diff --git a/course/validation.py b/course/validation.py index 39d135c2a9841bec229bcde20c6101155b321b64..9e28e5e5e8c576c394c92ca8014d6933045cd1b4 100644 --- a/course/validation.py +++ b/course/validation.py @@ -81,7 +81,7 @@ def validate_identifier(vctx, location, s, warning_only=False): msg = (string_concat( _("invalid identifier"), " '%(string)s'") - % {'location': location, 'string': s}) + % {"location": location, "string": s}) vctx.add_warning(location, msg) else: @@ -89,7 +89,7 @@ def validate_identifier(vctx, location, s, warning_only=False): "%(location)s: ", _("invalid identifier"), " '%(string)s'") - % {'location': location, 'string': s}) + % {"location": location, "string": s}) raise ValidationError(msg) @@ -106,7 +106,7 @@ def validate_role(vctx, location, role): raise ValidationError( string_concat("%(location)s: ", _("invalid role '%(role)s'")) - % {'location': location, 'role': role}) + % {"location": location, "role": role}) def validate_facility(vctx, location, facility): @@ -139,7 +139,7 @@ def validate_participationtag(vctx, location, participationtag): from course.models import ParticipationTag return list( ParticipationTag.objects.filter(course=vctx.course) - .values_list('name', flat=True)) + .values_list("name", flat=True)) ptag_list = get_ptag_list(vctx) if participationtag not in ptag_list: @@ -195,7 +195,7 @@ def validate_struct( raise ValidationError( string_concat("%(location)s: ", _("attribute '%(attr)s' missing")) - % {'location': location, 'attr': attr}) + % {"location": location, "attr": attr}) else: present_attrs.remove(attr) val = getattr(obj, attr) @@ -212,10 +212,10 @@ def validate_struct( "wrong type: got '%(name)s', " "expected '%(allowed)s'")) % { - 'location': location, - 'attr': attr, - 'name': type(val).__name__, - 'allowed': escape(str(allowed_types))}) + "location": location, + "attr": attr, + "name": type(val).__name__, + "allowed": escape(str(allowed_types))}) if is_markup: validate_markup(vctx, "%s: attribute %s" % (location, attr), val) @@ -224,7 +224,7 @@ def validate_struct( raise ValidationError( string_concat("%(location)s: ", _("extraneous attribute(s) '%(attr)s'")) - % {'location': location, 'attr': ",".join(present_attrs)}) + % {"location": location, "attr": ",".join(present_attrs)}) datespec_types = (datetime.date, str, datetime.datetime) @@ -298,7 +298,7 @@ def validate_markup(vctx, location, markup_str): raise ValidationError( "%(location)s: %(err_type)s: %(err_str)s" % { - 'location': location, + "location": location, "err_type": tp.__name__, "err_str": str(e)}) @@ -399,7 +399,7 @@ def validate_page_chunk(vctx, location, chunk): raise ValidationError( string_concat("%(location)s: ", _("no title present")) - % {'location': location}) + % {"location": location}) if hasattr(chunk, "rules"): for i, rule in enumerate(chunk.rules): @@ -431,7 +431,7 @@ def validate_staticpage_desc(vctx, location, page_desc): raise ValidationError( string_concat("%(location)s: ", _("must have either 'chunks' or 'content'")) - % {'location': location}) + % {"location": location}) # }}} @@ -458,7 +458,7 @@ def validate_staticpage_desc(vctx, location, page_desc): string_concat( "%(location)s: ", _("chunk id '%(chunkid)s' not unique")) - % {'location': location, 'chunkid': chunk.id}) + % {"location": location, "chunkid": chunk.id}) chunk_ids.add(chunk.id) @@ -527,10 +527,10 @@ def validate_flow_page(vctx, location, page_desc): ": %(err_type)s: " "%(err_str)s
%(format_exc)s
") % { - 'location': location, + "location": location, "err_type": tp.__name__, # type: ignore "err_str": str(e), - 'format_exc': format_exc()}) + "format_exc": format_exc()}) def validate_flow_group(vctx, location, grp): @@ -553,7 +553,7 @@ def validate_flow_group(vctx, location, grp): string_concat( "%(location)s, ", _("group '%(group_id)s': group is empty")) - % {'location': location, 'group_id': grp.id}) + % {"location": location, "group_id": grp.id}) for i, page_desc in enumerate(grp.pages): validate_flow_page( @@ -569,11 +569,11 @@ def validate_flow_group(vctx, location, grp): "%(location)s, ", _("group '%(group_id)s': " "max_page_count is not positive")) - % {'location': location, 'group_id': grp.id}) + % {"location": location, "group_id": grp.id}) elif not hasattr(grp, "shuffle") and grp.max_page_count < len(grp.pages): vctx.add_warning( _("%(location)s, group '%(group_id)s': ") % { - 'location': location, 'group_id': grp.id}, + "location": location, "group_id": grp.id}, _("shuffle attribute will be required for groups with" "max_page_count in a future version. set " "'shuffle: False' to match current behavior.")) @@ -588,7 +588,7 @@ def validate_flow_group(vctx, location, grp): string_concat( "%(location)s: ", _("page id '%(page_desc_id)s' not unique")) - % {'location': location, 'page_desc_id': page_desc.id}) + % {"location": location, "page_desc_id": page_desc.id}) page_ids.add(page_desc.id) @@ -677,7 +677,7 @@ def validate_session_start_rule(vctx, location, nrule, tags): string_concat( "%(location)s: ", _("invalid tag '%(tag)s'")) - % {'location': location, 'tag': nrule.tag_session}) + % {"location": location, "tag": nrule.tag_session}) if hasattr(nrule, "default_expiration_mode"): from course.constants import FLOW_SESSION_EXPIRATION_MODE_CHOICES @@ -687,8 +687,8 @@ def validate_session_start_rule(vctx, location, nrule, tags): string_concat("%(location)s: ", _("invalid default expiration mode '%(expiremode)s'")) % { - 'location': location, - 'expiremode': nrule.default_expiration_mode}) + "location": location, + "expiremode": nrule.default_expiration_mode}) def validate_session_access_rule(vctx, location, arule, tags): @@ -752,7 +752,7 @@ def validate_session_access_rule(vctx, location, arule, tags): string_concat( "%(location)s: ", _("invalid tag '%(tag)s'")) - % {'location': location, 'tag': arule.if_has_tag}) + % {"location": location, "tag": arule.if_has_tag}) if hasattr(arule, "if_expiration_mode"): if arule.if_expiration_mode not in dict( @@ -761,8 +761,8 @@ def validate_session_access_rule(vctx, location, arule, tags): string_concat("%(location)s: ", _("invalid expiration mode '%(expiremode)s'")) % { - 'location': location, - 'expiremode': arule.if_expiration_mode}) + "location": location, + "expiremode": arule.if_expiration_mode}) for j, perm in enumerate(arule.permissions): validate_flow_permission( @@ -879,7 +879,7 @@ def validate_session_grading_rule( string_concat( "%(location)s: ", _("invalid tag '%(tag)s'")) - % {'location': location, 'tag': grule.if_has_tag}) + % {"location": location, "tag": grule.if_has_tag}) has_conditionals = True if hasattr(grule, "due"): @@ -929,7 +929,7 @@ def validate_flow_rules(vctx, location, rules): "the 'rules' block itself.")) raise ValidationError( string_concat("%(location)s: ", error_msg) - % {'location': location}) + % {"location": location}) tags = getattr(rules, "tags", []) @@ -969,8 +969,8 @@ def validate_flow_rules(vctx, location, rules): "must have grading rules with a " "grade_aggregation_strategy")) % { - 'location': location, - 'identifier': rules.grade_identifier}) + "location": location, + "identifier": rules.grade_identifier}) from course.constants import GRADE_AGGREGATION_STRATEGY_CHOICES if ( @@ -993,7 +993,7 @@ def validate_flow_rules(vctx, location, rules): string_concat("%(location)s: ", _("'grading' block is required if grade_identifier " "is not null/None.")) - % {'location': location}) + % {"location": location}) else: has_conditionals = None @@ -1043,7 +1043,7 @@ def validate_flow_permission(vctx, location, permission): raise ValidationError( string_concat("%(location)s: ", _("invalid flow permission '%(permission)s'")) - % {'location': location, 'permission': permission}) + % {"location": location, "permission": permission}) # }}} @@ -1082,7 +1082,7 @@ def validate_flow_desc(vctx, location, flow_desc): raise ValidationError( string_concat("%(location)s: ", _("must have either 'groups' or 'pages'")) - % {'location': location}) + % {"location": location}) # }}} @@ -1106,9 +1106,9 @@ def validate_flow_desc(vctx, location, flow_desc): _("group %(group_index)d ('%(group_id)s'): " "'pages' is not a list")) % { - 'location': location, - 'group_index': i+1, - 'group_id': grp.id}) + "location": location, + "group_index": i+1, + "group_id": grp.id}) for page in grp.pages: group_has_page = flow_has_page = True @@ -1121,9 +1121,9 @@ def validate_flow_desc(vctx, location, flow_desc): _("group %(group_index)d ('%(group_id)s'): " "no pages found")) % { - 'location': location, - 'group_index': i+1, - 'group_id': grp.id}) + "location": location, + "group_index": i+1, + "group_id": grp.id}) if not flow_has_page: raise ValidationError(_("%s: no pages found") @@ -1140,7 +1140,7 @@ def validate_flow_desc(vctx, location, flow_desc): raise ValidationError( string_concat("%(location)s: ", _("group id '%(group_id)s' not unique")) - % {'location': location, 'group_id': grp.id}) + % {"location": location, "group_id": grp.id}) group_ids.add(grp.id) @@ -1247,7 +1247,7 @@ def get_yaml_from_repo_safely(repo, full_name, commit_sha): raise ValidationError( "%(fullname)s: %(err_type)s: %(err_str)s" % { - 'fullname': full_name, + "fullname": full_name, "err_type": tp.__name__, "err_str": str(e)}) @@ -1521,7 +1521,7 @@ def validate_course_content(repo, course_file, events_file, pass else: vctx.add_warning( - 'media/', _( + "media/", _( "Your course repository has a 'media/' directory. " "Linking to media files using 'media:' is discouraged. " "Use the 'repo:' and 'repocur:' linkng schemes instead.")) diff --git a/course/versioning.py b/course/versioning.py index 8bbe1442332d5937c6f58b6fd905951381e5ab00..64c669b1ef0ad2be71702e6deb69a70e30e8812c 100644 --- a/course/versioning.py +++ b/course/versioning.py @@ -401,7 +401,7 @@ def run_course_update_command( "") % ("".join( "
  • %(location)s: %(warningtext)s
  • " - % {'location': w.location, 'warningtext': w.text} + % {"location": w.location, "warningtext": w.text} for w in warnings))) # }}} @@ -622,9 +622,9 @@ def call_wsgi_app( assert request.environ == request.META environ = request.environ.copy() #if len(args) > 0: - assert environ['PATH_INFO'].startswith(prefix) - environ['SCRIPT_NAME'] += prefix - environ['PATH_INFO'] = environ['PATH_INFO'][len(prefix):] + assert environ["PATH_INFO"].startswith(prefix) + environ["SCRIPT_NAME"] += prefix + environ["PATH_INFO"] = environ["PATH_INFO"][len(prefix):] headers_set: List[Tuple[Text, Text]] = [] headers_sent: List[bool] = [] @@ -648,7 +648,7 @@ def call_wsgi_app( if headers_set: raise AssertionError("start_response() called again " "without exc_info") - response.status_code = int(status.split(' ', 1)[0]) + response.status_code = int(status.split(" ", 1)[0]) headers_set[:] = headers # Django provides no way to set the reason phrase (#12747). return write @@ -659,9 +659,9 @@ def call_wsgi_app( if data: write(data) if not headers_sent: - write('') + write("") finally: - if hasattr(result, 'close'): + if hasattr(result, "close"): result.close() return response diff --git a/course/views.py b/course/views.py index 95d5781475e2865b83782d4aac5817092ccb2e48..594208090d3fa61374a325e83d002602773eb212 100644 --- a/course/views.py +++ b/course/views.py @@ -368,7 +368,7 @@ class FakeTimeForm(StyledForm): time = forms.DateTimeField( widget=DateTimePicker( options={"format": "YYYY-MM-DD HH:mm", "sideBySide": True}), - label=_('Time')) + label=_("Time")) def __init__(self, *args, **kwargs): super(FakeTimeForm, self).__init__(*args, **kwargs) @@ -854,8 +854,8 @@ def grant_exception_stage_2(pctx, participation_id, flow_id): "'%(flow_id)s'."), "") % { - 'participation': participation, - 'flow_id': flow_id}) + "participation": participation, + "flow_id": flow_id}) from course.content import get_flow_desc try: @@ -946,11 +946,11 @@ def grant_exception_stage_2(pctx, participation_id, flow_id): _("A new session%(tag)s was created for '%(participation)s' " "for '%(flow_id)s'.") % { - 'tag': + "tag": _(" tagged '%s'") % access_rules_tag if access_rules_tag is not None else "", - 'participation': participation, - 'flow_id': flow_id}) + "participation": participation, + "flow_id": flow_id}) elif exception_form.is_valid() and "next" in request.POST: # type: ignore return redirect( @@ -1215,7 +1215,7 @@ def grant_exception_stage_3(pctx, participation_id, flow_id, session_id): descr = gettext("Granted exception") if form.cleaned_data["credit_percent"] is not None: - descr += string_concat(" (%.1f%% ", gettext('credit'), ")") \ + descr += string_concat(" (%.1f%% ", gettext("credit"), ")") \ % form.cleaned_data["credit_percent"] due_local_naive = due @@ -1265,9 +1265,9 @@ def grant_exception_stage_3(pctx, participation_id, flow_id, session_id): "'%(exception_type)s' exception granted to " "'%(participation)s' for '%(flow_id)s'.") % { - 'exception_type': exc, - 'participation': participation, - 'flow_id': flow_id}) + "exception_type": exc, + "participation": participation, + "flow_id": flow_id}) else: if session_access_rules_tag_changed: messages.add_message( @@ -1277,8 +1277,8 @@ def grant_exception_stage_3(pctx, participation_id, flow_id, session_id): "session of '%(participation)s' " "for '%(flow_id)s'.") % { - 'participation': participation, - 'flow_id': flow_id}) + "participation": participation, + "flow_id": flow_id}) else: messages.add_message(pctx.request, messages.WARNING, _( @@ -1286,8 +1286,8 @@ def grant_exception_stage_3(pctx, participation_id, flow_id, session_id): "session of '%(participation)s' " "for '%(flow_id)s'.") % { - 'participation': participation, - 'flow_id': flow_id}) + "participation": participation, + "flow_id": flow_id}) return redirect( "relate-grant_exception", pctx.course.identifier) @@ -1317,9 +1317,9 @@ def grant_exception_stage_3(pctx, participation_id, flow_id, session_id): "for '%(flow_id)s' (session %(session)s)."), "") % { - 'participation': participation, - 'flow_id': flow_id, - 'session': strify_session_for_exception(session)}, + "participation": participation, + "flow_id": flow_id, + "session": strify_session_for_exception(session)}, }) # }}} @@ -1428,7 +1428,7 @@ def edit_course(pctx): request = pctx.request instance = pctx.course - if request.method == 'POST': + if request.method == "POST": form = EditCourseForm(request.POST, instance=pctx.course) if form.is_valid(): if form.has_changed(): diff --git a/local_settings_example.py b/local_settings_example.py index 56ab7d45259bb2099347da2c8f4c3ed5c60cd95b..9e1642b72cef65bb173acb31368b7e7f3d61ce0d 100644 --- a/local_settings_example.py +++ b/local_settings_example.py @@ -2,7 +2,7 @@ # {{{ database and site -SECRET_KEY = '' +SECRET_KEY = "" ALLOWED_HOSTS = [ "relate.example.com", @@ -23,13 +23,13 @@ from django.utils.translation import gettext_noop # noqa # database will be used, which is not recommended for production use. # # DATABASES = { -# 'default': { -# 'ENGINE': 'django.db.backends.postgresql', -# 'NAME': 'relate', -# 'USER': 'relate', -# 'PASSWORD': '', -# 'HOST': '127.0.0.1', -# 'PORT': '5432', +# "default": { +# "ENGINE": "django.db.backends.postgresql", +# "NAME": "relate", +# "USER": "relate", +# "PASSWORD": '', +# "HOST": '127.0.0.1', +# "PORT": '5432', # } # } @@ -46,9 +46,9 @@ from django.utils.translation import gettext_noop # noqa # broken in Python 33, as of 2016-08-01. # # CACHES = { -# 'default': { -# 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', -# 'LOCATION': '127.0.0.1:11211', +# "default": { +# "BACKEND": "django.core.cache.backends.memcached.PyLibMCCache", +# "LOCATION": '127.0.0.1:11211', # } # } @@ -67,7 +67,7 @@ TIME_ZONE = "America/Chicago" # should be enough to satisfy this requirement. # # apt-get install rabbitmq-server -CELERY_BROKER_URL = 'amqp://' +CELERY_BROKER_URL = "amqp://" # }}} @@ -80,7 +80,7 @@ CELERY_BROKER_URL = 'amqp://' # which makes sense for development, but you probably want to change this # in production. # -# The 'course identifiers' you enter will be directory names below this root. +# The "course identifiers" you enter will be directory names below this root. # WARNING: The default, "..", is insecure (but convenient for development). # It will put course repos alongside the relate git checkout in the parent @@ -93,9 +93,9 @@ GIT_ROOT = "git-roots" # {{{ email -EMAIL_HOST = '127.0.0.1' -EMAIL_HOST_USER = '' -EMAIL_HOST_PASSWORD = '' +EMAIL_HOST = "127.0.0.1" +EMAIL_HOST_USER = "" +EMAIL_HOST_PASSWORD = "" EMAIL_PORT = 25 EMAIL_USE_TLS = False @@ -126,58 +126,58 @@ if RELATE_ENABLE_MULTIPLE_SMTP: # For automatic email sent by site. "robot": { # You can use your preferred email backend. - 'backend': 'djcelery_email.backends.CeleryEmailBackend', - 'host': 'smtp.gmail.com', - 'username': 'blah@blah.com', - 'password': 'password', - 'port': 587, - 'use_tls': True, + "backend": "djcelery_email.backends.CeleryEmailBackend", + "host": "smtp.gmail.com", + "username": "blah@blah.com", + "password": "password", + "port": 587, + "use_tls": True, }, # For emails that expect no reply for recipients, e.g., registration, # reset password, etc. "no_reply": { - 'host': 'smtp.gmail.com', - 'username': 'blah@blah.com', - 'password': 'password', - 'port': 587, - 'use_tls': True, + "host": "smtp.gmail.com", + "username": "blah@blah.com", + "password": "password", + "port": 587, + "use_tls": True, }, # For sending notifications like submission of flow sessions. "notification": { - 'host': 'smtp.gmail.com', - 'username': 'blah@blah.com', - 'password': 'password', - 'port': 587, - 'use_tls': True, + "host": "smtp.gmail.com", + "username": "blah@blah.com", + "password": "password", + "port": 587, + "use_tls": True, }, # For sending feedback email to students in grading interface. "grader_feedback": { - 'host': 'smtp.gmail.com', - 'username': 'blah@blah.com', - 'password': 'password', - 'port': 587, - 'use_tls': True, + "host": "smtp.gmail.com", + "username": "blah@blah.com", + "password": "password", + "port": 587, + "use_tls": True, }, # For student to send email to course staff in flow pages "student_interact": { - 'host': 'smtp.gmail.com', - 'username': 'blah@blah.com', - 'password': 'password', - 'port': 587, - 'use_tls': True, + "host": "smtp.gmail.com", + "username": "blah@blah.com", + "password": "password", + "port": 587, + "use_tls": True, }, # For enrollment request email sent to course instructors "enroll": { - 'host': 'smtp.gmail.com', - 'username': 'blah@blah.com', - 'password': 'password', - 'port': 587, - 'use_tls': True, + "host": "smtp.gmail.com", + "username": "blah@blah.com", + "password": "password", + "port": 587, + "use_tls": True, }, } @@ -385,11 +385,11 @@ RELATE_SITE_ANNOUNCEMENT = None # }}} -# Uncomment this to enable i18n, change 'en-us' to locale name your language. +# Uncomment this to enable i18n, change "en-us" to locale name your language. # Make sure you have generated, translate and compile the message file of your -# language. If commented, RELATE will use default language 'en-us'. +# language. If commented, RELATE will use default language "en-us". -#LANGUAGE_CODE = 'en-us' +#LANGUAGE_CODE = "en-us" # You can (and it's recommended to) override Django's built-in LANGUAGES settings # if you want to filter languages allowed for course-specific languages. @@ -402,9 +402,9 @@ RELATE_SITE_ANNOUNCEMENT = None # the default "en-us". Otherwise translation of that language will not work. # LANGUAGES = [ -# ('en', 'English'), -# ('zh-hans', 'Simplified Chinese'), -# ('de', 'German'), +# ("en", "English"), +# ("zh-hans", "Simplified Chinese"), +# ("de", "German"), # ] # {{{ exams and testing @@ -455,84 +455,84 @@ if RELATE_SIGN_IN_BY_SAML2_ENABLED: import saml2.saml _BASEDIR = path.dirname(path.abspath(__file__)) - _BASE_URL = 'https://relate.cs.illinois.edu' + _BASE_URL = "https://relate.cs.illinois.edu" # see saml2-keygen.sh in this directory - _SAML_KEY_FILE = path.join(_BASEDIR, 'saml-config', 'sp-key.pem') - _SAML_CERT_FILE = path.join(_BASEDIR, 'saml-config', 'sp-cert.pem') + _SAML_KEY_FILE = path.join(_BASEDIR, "saml-config", "sp-key.pem") + _SAML_CERT_FILE = path.join(_BASEDIR, "saml-config", "sp-cert.pem") SAML_ATTRIBUTE_MAPPING = { - 'eduPersonPrincipalName': ('username',), - 'iTrustUIN': ('institutional_id',), - 'mail': ('email',), - 'givenName': ('first_name', ), - 'sn': ('last_name', ), + "eduPersonPrincipalName": ("username",), + "iTrustUIN": ("institutional_id",), + "mail": ("email",), + "givenName": ("first_name", ), + "sn": ("last_name", ), } - SAML_DJANGO_USER_MAIN_ATTRIBUTE = 'username' - SAML_DJANGO_USER_MAIN_ATTRIBUTE_LOOKUP = '__iexact' + SAML_DJANGO_USER_MAIN_ATTRIBUTE = "username" + SAML_DJANGO_USER_MAIN_ATTRIBUTE_LOOKUP = "__iexact" saml_idp = { # Find the entity ID of your IdP and make this the key here: - 'urn:mace:incommon:uiuc.edu': { - 'single_sign_on_service': { + "urn:mace:incommon:uiuc.edu": { + "single_sign_on_service": { # Add the POST and REDIRECT bindings for the sign on service here: saml2.BINDING_HTTP_POST: - 'https://shibboleth.illinois.edu/idp/profile/SAML2/POST/SSO', + "https://shibboleth.illinois.edu/idp/profile/SAML2/POST/SSO", saml2.BINDING_HTTP_REDIRECT: - 'https://shibboleth.illinois.edu/idp/profile/SAML2/Redirect/SSO', + "https://shibboleth.illinois.edu/idp/profile/SAML2/Redirect/SSO", }, - 'single_logout_service': { + "single_logout_service": { # And the REDIRECT binding for the logout service here: saml2.BINDING_HTTP_REDIRECT: - 'https://shibboleth.illinois.edu/idp/logout.jsp', # noqa + "https://shibboleth.illinois.edu/idp/logout.jsp", # noqa }, }, } SAML_CONFIG = { # full path to the xmlsec1 binary programm - 'xmlsec_binary': '/usr/bin/xmlsec1', + "xmlsec_binary": "/usr/bin/xmlsec1", # your entity id, usually your subdomain plus the url to the metadata view # (usually no need to change) - 'entityid': _BASE_URL + '/saml2/metadata/', + "entityid": _BASE_URL + "/saml2/metadata/", # directory with attribute mapping # (already populated with samples from djangosaml2, usually no need to # change) - 'attribute_map_dir': path.join(_BASEDIR, 'saml-config', 'attribute-maps'), + "attribute_map_dir": path.join(_BASEDIR, "saml-config", "attribute-maps"), - 'allow_unknown_attributes': True, + "allow_unknown_attributes": True, # this block states what services we provide - 'service': { - 'sp': { - 'name': 'RELATE SAML2 SP', - 'name_id_format': saml2.saml.NAMEID_FORMAT_TRANSIENT, - 'endpoints': { + "service": { + "sp": { + "name": "RELATE SAML2 SP", + "name_id_format": saml2.saml.NAMEID_FORMAT_TRANSIENT, + "endpoints": { # url and binding to the assertion consumer service view # do not change the binding or service name - 'assertion_consumer_service': [ - (_BASE_URL + '/saml2/acs/', + "assertion_consumer_service": [ + (_BASE_URL + "/saml2/acs/", saml2.BINDING_HTTP_POST), ], # url and binding to the single logout service view # do not change the binding or service name - 'single_logout_service': [ - (_BASE_URL + '/saml2/ls/', + "single_logout_service": [ + (_BASE_URL + "/saml2/ls/", saml2.BINDING_HTTP_REDIRECT), - (_BASE_URL + '/saml2/ls/post', + (_BASE_URL + "/saml2/ls/post", saml2.BINDING_HTTP_POST), ], }, # attributes that this project needs to identify a user - 'required_attributes': ['uid'], + "required_attributes": ["uid"], # attributes that may be useful to have but not required - 'optional_attributes': ['eduPersonAffiliation'], + "optional_attributes": ["eduPersonAffiliation"], - 'idp': saml_idp, + "idp": saml_idp, }, }, @@ -543,44 +543,44 @@ if RELATE_SIGN_IN_BY_SAML2_ENABLED: # This particular file is public and lives at # https://discovery.itrust.illinois.edu/itrust-metadata/itrust-metadata.xml - 'metadata': { - 'local': [path.join(_BASEDIR, 'saml-config', 'itrust-metadata.xml')], + "metadata": { + "local": [path.join(_BASEDIR, "saml-config", "itrust-metadata.xml")], }, # set to 1 to output debugging information - 'debug': 1, + "debug": 1, # certificate and key - 'key_file': _SAML_KEY_FILE, - 'cert_file': _SAML_CERT_FILE, + "key_file": _SAML_KEY_FILE, + "cert_file": _SAML_CERT_FILE, - 'encryption_keypairs': [ + "encryption_keypairs": [ { - 'key_file': _SAML_KEY_FILE, - 'cert_file': _SAML_CERT_FILE, + "key_file": _SAML_KEY_FILE, + "cert_file": _SAML_CERT_FILE, } ], # own metadata settings - 'contact_person': [ - {'given_name': 'Andreas', - 'sur_name': 'Kloeckner', - 'company': 'CS - University of Illinois', - 'email_address': 'andreask@illinois.edu', - 'contact_type': 'technical'}, - {'given_name': 'Andreas', - 'sur_name': 'Kloeckner', - 'company': 'CS - University of Illinois', - 'email_address': 'andreask@illinois.edu', - 'contact_type': 'administrative'}, + "contact_person": [ + {"given_name": "Andreas", + "sur_name": "Kloeckner", + "company": "CS - University of Illinois", + "email_address": "andreask@illinois.edu", + "contact_type": "technical"}, + {"given_name": "Andreas", + "sur_name": "Kloeckner", + "company": "CS - University of Illinois", + "email_address": "andreask@illinois.edu", + "contact_type": "administrative"}, ], # you can set multilanguage information here - 'organization': { - 'name': [('RELATE', 'en')], - 'display_name': [('RELATE', 'en')], - 'url': [(_BASE_URL, 'en')], + "organization": { + "name": [("RELATE", "en")], + "display_name": [("RELATE", "en")], + "url": [(_BASE_URL, "en")], }, - 'valid_for': 24, # how long is our metadata valid + "valid_for": 24, # how long is our metadata valid } # }}} diff --git a/poetry.lock b/poetry.lock index 4802f96b254b722e238e6b19a8b3ece396fa2b2c..843ed9a6be1f273172ced866bf11f4bbbbe1edf3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -60,12 +60,13 @@ description = "Classes Without Boilerplate" name = "attrs" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.1.0" +version = "20.2.0" [package.extras] dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"] docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] [[package]] category = "dev" @@ -116,7 +117,7 @@ description = "An easy safelist-based HTML-sanitizing tool." name = "bleach" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "3.1.5" +version = "3.2.1" [package.dependencies] packaging = "*" @@ -185,7 +186,7 @@ description = "Foreign Function Interface for Python calling C code." name = "cffi" optional = false python-versions = "*" -version = "1.14.2" +version = "1.14.3" [package.dependencies] pycparser = "*" @@ -232,7 +233,7 @@ description = "Code coverage measurement for Python" name = "coverage" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "5.2.1" +version = "5.3" [package.extras] toml = ["toml"] @@ -509,7 +510,7 @@ description = "Faker is a Python package that generates fake data for you." name = "faker" optional = false python-versions = ">=3.5" -version = "4.1.2" +version = "4.1.3" [package.dependencies] python-dateutil = ">=2.4" @@ -543,6 +544,17 @@ version = "1.0.2" [package.dependencies] flake8 = "*" +[[package]] +category = "dev" +description = "Flake8 lint for quotes." +name = "flake8-quotes" +optional = false +python-versions = "*" +version = "3.2.0" + +[package.dependencies] +flake8 = "*" + [[package]] category = "main" description = "Clean single-source support for Python 3 and 2" @@ -885,7 +897,7 @@ description = "NumPy is the fundamental package for array computing with Python. name = "numpy" optional = false python-versions = ">=3.6" -version = "1.19.1" +version = "1.19.2" [[package]] category = "main" @@ -1039,7 +1051,7 @@ description = "Pygments is a syntax highlighting package written in Python." name = "pygments" optional = false python-versions = ">=3.5" -version = "2.6.1" +version = "2.7.1" [[package]] category = "main" @@ -1099,11 +1111,8 @@ category = "main" description = "Persistent/Functional/Immutable data structures" name = "pyrsistent" optional = false -python-versions = "*" -version = "0.16.0" - -[package.dependencies] -six = "*" +python-versions = ">=3.5" +version = "0.17.3" [[package]] category = "main" @@ -1172,7 +1181,7 @@ description = "A Django plugin for pytest." name = "pytest-django" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "3.9.0" +version = "3.10.0" [package.dependencies] pytest = ">=3.6" @@ -1563,7 +1572,8 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["jaraco.itertools", "func-timeout"] [metadata] -content-hash = "a644567b58bc96e33c93f01ae7b8e8514463be2bf61f6cc0405ce8ea79425230" +content-hash = "0bb8722af54222576fac26622129b90f96ac47f16befba551a59112ef5e81b68" +lock-version = "1.0" python-versions = "^3.6" [metadata.files] @@ -1592,8 +1602,8 @@ atomicwrites = [ {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-20.1.0-py2.py3-none-any.whl", hash = "sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff"}, - {file = "attrs-20.1.0.tar.gz", hash = "sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a"}, + {file = "attrs-20.2.0-py2.py3-none-any.whl", hash = "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"}, + {file = "attrs-20.2.0.tar.gz", hash = "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594"}, ] babel = [ {file = "Babel-2.8.0-py2.py3-none-any.whl", hash = "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"}, @@ -1617,8 +1627,8 @@ billiard = [ {file = "billiard-3.6.3.0.tar.gz", hash = "sha256:d91725ce6425f33a97dfa72fb6bfef0e47d4652acd98a032bd1a7fbf06d5fa6a"}, ] bleach = [ - {file = "bleach-3.1.5-py2.py3-none-any.whl", hash = "sha256:2bce3d8fab545a6528c8fa5d9f9ae8ebc85a56da365c7f85180bfe96a35ef22f"}, - {file = "bleach-3.1.5.tar.gz", hash = "sha256:3c4c520fdb9db59ef139915a5db79f8b51bc2a7257ea0389f30c846883430a4b"}, + {file = "bleach-3.2.1-py2.py3-none-any.whl", hash = "sha256:9f8ccbeb6183c6e6cddea37592dfb0167485c1e3b13b3363bc325aa8bda3adbd"}, + {file = "bleach-3.2.1.tar.gz", hash = "sha256:52b5919b81842b1854196eaae5ca29679a2f2e378905c346d3ca8227c2c66080"}, ] celery = [ {file = "celery-4.4.7-py2.py3-none-any.whl", hash = "sha256:a92e1d56e650781fb747032a3997d16236d037c8199eacd5217d1a72893bca45"}, @@ -1629,34 +1639,42 @@ certifi = [ {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, ] cffi = [ - {file = "cffi-1.14.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:da9d3c506f43e220336433dffe643fbfa40096d408cb9b7f2477892f369d5f82"}, - {file = "cffi-1.14.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23e44937d7695c27c66a54d793dd4b45889a81b35c0751ba91040fe825ec59c4"}, - {file = "cffi-1.14.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:0da50dcbccd7cb7e6c741ab7912b2eff48e85af217d72b57f80ebc616257125e"}, - {file = "cffi-1.14.2-cp27-cp27m-win32.whl", hash = "sha256:76ada88d62eb24de7051c5157a1a78fd853cca9b91c0713c2e973e4196271d0c"}, - {file = "cffi-1.14.2-cp27-cp27m-win_amd64.whl", hash = "sha256:15a5f59a4808f82d8ec7364cbace851df591c2d43bc76bcbe5c4543a7ddd1bf1"}, - {file = "cffi-1.14.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:e4082d832e36e7f9b2278bc774886ca8207346b99f278e54c9de4834f17232f7"}, - {file = "cffi-1.14.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:57214fa5430399dffd54f4be37b56fe22cedb2b98862550d43cc085fb698dc2c"}, - {file = "cffi-1.14.2-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:6843db0343e12e3f52cc58430ad559d850a53684f5b352540ca3f1bc56df0731"}, - {file = "cffi-1.14.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:577791f948d34d569acb2d1add5831731c59d5a0c50a6d9f629ae1cefd9ca4a0"}, - {file = "cffi-1.14.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:8662aabfeab00cea149a3d1c2999b0731e70c6b5bac596d95d13f643e76d3d4e"}, - {file = "cffi-1.14.2-cp35-cp35m-win32.whl", hash = "sha256:837398c2ec00228679513802e3744d1e8e3cb1204aa6ad408b6aff081e99a487"}, - {file = "cffi-1.14.2-cp35-cp35m-win_amd64.whl", hash = "sha256:bf44a9a0141a082e89c90e8d785b212a872db793a0080c20f6ae6e2a0ebf82ad"}, - {file = "cffi-1.14.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:29c4688ace466a365b85a51dcc5e3c853c1d283f293dfcc12f7a77e498f160d2"}, - {file = "cffi-1.14.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:99cc66b33c418cd579c0f03b77b94263c305c389cb0c6972dac420f24b3bf123"}, - {file = "cffi-1.14.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:65867d63f0fd1b500fa343d7798fa64e9e681b594e0a07dc934c13e76ee28fb1"}, - {file = "cffi-1.14.2-cp36-cp36m-win32.whl", hash = "sha256:f5033952def24172e60493b68717792e3aebb387a8d186c43c020d9363ee7281"}, - {file = "cffi-1.14.2-cp36-cp36m-win_amd64.whl", hash = "sha256:7057613efefd36cacabbdbcef010e0a9c20a88fc07eb3e616019ea1692fa5df4"}, - {file = "cffi-1.14.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6539314d84c4d36f28d73adc1b45e9f4ee2a89cdc7e5d2b0a6dbacba31906798"}, - {file = "cffi-1.14.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:672b539db20fef6b03d6f7a14b5825d57c98e4026401fce838849f8de73fe4d4"}, - {file = "cffi-1.14.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:95e9094162fa712f18b4f60896e34b621df99147c2cee216cfa8f022294e8e9f"}, - {file = "cffi-1.14.2-cp37-cp37m-win32.whl", hash = "sha256:b9aa9d8818c2e917fa2c105ad538e222a5bce59777133840b93134022a7ce650"}, - {file = "cffi-1.14.2-cp37-cp37m-win_amd64.whl", hash = "sha256:e4b9b7af398c32e408c00eb4e0d33ced2f9121fd9fb978e6c1b57edd014a7d15"}, - {file = "cffi-1.14.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e613514a82539fc48291d01933951a13ae93b6b444a88782480be32245ed4afa"}, - {file = "cffi-1.14.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9b219511d8b64d3fa14261963933be34028ea0e57455baf6781fe399c2c3206c"}, - {file = "cffi-1.14.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:c0b48b98d79cf795b0916c57bebbc6d16bb43b9fc9b8c9f57f4cf05881904c75"}, - {file = "cffi-1.14.2-cp38-cp38-win32.whl", hash = "sha256:15419020b0e812b40d96ec9d369b2bc8109cc3295eac6e013d3261343580cc7e"}, - {file = "cffi-1.14.2-cp38-cp38-win_amd64.whl", hash = "sha256:12a453e03124069b6896107ee133ae3ab04c624bb10683e1ed1c1663df17c13c"}, - {file = "cffi-1.14.2.tar.gz", hash = "sha256:ae8f34d50af2c2154035984b8b5fc5d9ed63f32fe615646ab435b05b132ca91b"}, + {file = "cffi-1.14.3-2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3eeeb0405fd145e714f7633a5173318bd88d8bbfc3dd0a5751f8c4f70ae629bc"}, + {file = "cffi-1.14.3-2-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:cb763ceceae04803adcc4e2d80d611ef201c73da32d8f2722e9d0ab0c7f10768"}, + {file = "cffi-1.14.3-2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:44f60519595eaca110f248e5017363d751b12782a6f2bd6a7041cba275215f5d"}, + {file = "cffi-1.14.3-2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c53af463f4a40de78c58b8b2710ade243c81cbca641e34debf3396a9640d6ec1"}, + {file = "cffi-1.14.3-2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:33c6cdc071ba5cd6d96769c8969a0531be2d08c2628a0143a10a7dcffa9719ca"}, + {file = "cffi-1.14.3-2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c11579638288e53fc94ad60022ff1b67865363e730ee41ad5e6f0a17188b327a"}, + {file = "cffi-1.14.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:3cb3e1b9ec43256c4e0f8d2837267a70b0e1ca8c4f456685508ae6106b1f504c"}, + {file = "cffi-1.14.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f0620511387790860b249b9241c2f13c3a80e21a73e0b861a2df24e9d6f56730"}, + {file = "cffi-1.14.3-cp27-cp27m-win32.whl", hash = "sha256:005f2bfe11b6745d726dbb07ace4d53f057de66e336ff92d61b8c7e9c8f4777d"}, + {file = "cffi-1.14.3-cp27-cp27m-win_amd64.whl", hash = "sha256:2f9674623ca39c9ebe38afa3da402e9326c245f0f5ceff0623dccdac15023e05"}, + {file = "cffi-1.14.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:09e96138280241bd355cd585148dec04dbbedb4f46128f340d696eaafc82dd7b"}, + {file = "cffi-1.14.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:3363e77a6176afb8823b6e06db78c46dbc4c7813b00a41300a4873b6ba63b171"}, + {file = "cffi-1.14.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:0ef488305fdce2580c8b2708f22d7785ae222d9825d3094ab073e22e93dfe51f"}, + {file = "cffi-1.14.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:0b1ad452cc824665ddc682400b62c9e4f5b64736a2ba99110712fdee5f2505c4"}, + {file = "cffi-1.14.3-cp35-cp35m-win32.whl", hash = "sha256:85ba797e1de5b48aa5a8427b6ba62cf69607c18c5d4eb747604b7302f1ec382d"}, + {file = "cffi-1.14.3-cp35-cp35m-win_amd64.whl", hash = "sha256:e66399cf0fc07de4dce4f588fc25bfe84a6d1285cc544e67987d22663393926d"}, + {file = "cffi-1.14.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:15f351bed09897fbda218e4db5a3d5c06328862f6198d4fb385f3e14e19decb3"}, + {file = "cffi-1.14.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4d7c26bfc1ea9f92084a1d75e11999e97b62d63128bcc90c3624d07813c52808"}, + {file = "cffi-1.14.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:23e5d2040367322824605bc29ae8ee9175200b92cb5483ac7d466927a9b3d537"}, + {file = "cffi-1.14.3-cp36-cp36m-win32.whl", hash = "sha256:a624fae282e81ad2e4871bdb767e2c914d0539708c0f078b5b355258293c98b0"}, + {file = "cffi-1.14.3-cp36-cp36m-win_amd64.whl", hash = "sha256:de31b5164d44ef4943db155b3e8e17929707cac1e5bd2f363e67a56e3af4af6e"}, + {file = "cffi-1.14.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f92cdecb618e5fa4658aeb97d5eb3d2f47aa94ac6477c6daf0f306c5a3b9e6b1"}, + {file = "cffi-1.14.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:22399ff4870fb4c7ef19fff6eeb20a8bbf15571913c181c78cb361024d574579"}, + {file = "cffi-1.14.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f4eae045e6ab2bb54ca279733fe4eb85f1effda392666308250714e01907f394"}, + {file = "cffi-1.14.3-cp37-cp37m-win32.whl", hash = "sha256:b0358e6fefc74a16f745afa366acc89f979040e0cbc4eec55ab26ad1f6a9bfbc"}, + {file = "cffi-1.14.3-cp37-cp37m-win_amd64.whl", hash = "sha256:6642f15ad963b5092d65aed022d033c77763515fdc07095208f15d3563003869"}, + {file = "cffi-1.14.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:2791f68edc5749024b4722500e86303a10d342527e1e3bcac47f35fbd25b764e"}, + {file = "cffi-1.14.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:529c4ed2e10437c205f38f3691a68be66c39197d01062618c55f74294a4a4828"}, + {file = "cffi-1.14.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8f0f1e499e4000c4c347a124fa6a27d37608ced4fe9f7d45070563b7c4c370c9"}, + {file = "cffi-1.14.3-cp38-cp38-win32.whl", hash = "sha256:3b8eaf915ddc0709779889c472e553f0d3e8b7bdf62dab764c8921b09bf94522"}, + {file = "cffi-1.14.3-cp38-cp38-win_amd64.whl", hash = "sha256:bbd2f4dfee1079f76943767fce837ade3087b578aeb9f69aec7857d5bf25db15"}, + {file = "cffi-1.14.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:cc75f58cdaf043fe6a7a6c04b3b5a0e694c6a9e24050967747251fb80d7bce0d"}, + {file = "cffi-1.14.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:bf39a9e19ce7298f1bd6a9758fa99707e9e5b1ebe5e90f2c3913a47bc548747c"}, + {file = "cffi-1.14.3-cp39-cp39-win32.whl", hash = "sha256:d80998ed59176e8cba74028762fbd9b9153b9afc71ea118e63bbf5d4d0f9552b"}, + {file = "cffi-1.14.3-cp39-cp39-win_amd64.whl", hash = "sha256:c150eaa3dadbb2b5339675b88d4573c1be3cb6f2c33a6c83387e10cc0bf05bd3"}, + {file = "cffi-1.14.3.tar.gz", hash = "sha256:f92f789e4f9241cd262ad7a555ca2c648a98178a953af117ef7fad46aa1d5591"}, ] chardet = [ {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, @@ -1676,40 +1694,40 @@ colorama = [ {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, ] coverage = [ - {file = "coverage-5.2.1-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:40f70f81be4d34f8d491e55936904db5c527b0711b2a46513641a5729783c2e4"}, - {file = "coverage-5.2.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:675192fca634f0df69af3493a48224f211f8db4e84452b08d5fcebb9167adb01"}, - {file = "coverage-5.2.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2fcc8b58953d74d199a1a4d633df8146f0ac36c4e720b4a1997e9b6327af43a8"}, - {file = "coverage-5.2.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:64c4f340338c68c463f1b56e3f2f0423f7b17ba6c3febae80b81f0e093077f59"}, - {file = "coverage-5.2.1-cp27-cp27m-win32.whl", hash = "sha256:52f185ffd3291196dc1aae506b42e178a592b0b60a8610b108e6ad892cfc1bb3"}, - {file = "coverage-5.2.1-cp27-cp27m-win_amd64.whl", hash = "sha256:30bc103587e0d3df9e52cd9da1dd915265a22fad0b72afe54daf840c984b564f"}, - {file = "coverage-5.2.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:9ea749fd447ce7fb1ac71f7616371f04054d969d412d37611716721931e36efd"}, - {file = "coverage-5.2.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ce7866f29d3025b5b34c2e944e66ebef0d92e4a4f2463f7266daa03a1332a651"}, - {file = "coverage-5.2.1-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:4869ab1c1ed33953bb2433ce7b894a28d724b7aa76c19b11e2878034a4e4680b"}, - {file = "coverage-5.2.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a3ee9c793ffefe2944d3a2bd928a0e436cd0ac2d9e3723152d6fd5398838ce7d"}, - {file = "coverage-5.2.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:28f42dc5172ebdc32622a2c3f7ead1b836cdbf253569ae5673f499e35db0bac3"}, - {file = "coverage-5.2.1-cp35-cp35m-win32.whl", hash = "sha256:e26c993bd4b220429d4ec8c1468eca445a4064a61c74ca08da7429af9bc53bb0"}, - {file = "coverage-5.2.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4186fc95c9febeab5681bc3248553d5ec8c2999b8424d4fc3a39c9cba5796962"}, - {file = "coverage-5.2.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:b360d8fd88d2bad01cb953d81fd2edd4be539df7bfec41e8753fe9f4456a5082"}, - {file = "coverage-5.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:1adb6be0dcef0cf9434619d3b892772fdb48e793300f9d762e480e043bd8e716"}, - {file = "coverage-5.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:098a703d913be6fbd146a8c50cc76513d726b022d170e5e98dc56d958fd592fb"}, - {file = "coverage-5.2.1-cp36-cp36m-win32.whl", hash = "sha256:962c44070c281d86398aeb8f64e1bf37816a4dfc6f4c0f114756b14fc575621d"}, - {file = "coverage-5.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1ed2bdb27b4c9fc87058a1cb751c4df8752002143ed393899edb82b131e0546"}, - {file = "coverage-5.2.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:c890728a93fffd0407d7d37c1e6083ff3f9f211c83b4316fae3778417eab9811"}, - {file = "coverage-5.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:538f2fd5eb64366f37c97fdb3077d665fa946d2b6d95447622292f38407f9258"}, - {file = "coverage-5.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:27ca5a2bc04d68f0776f2cdcb8bbd508bbe430a7bf9c02315cd05fb1d86d0034"}, - {file = "coverage-5.2.1-cp37-cp37m-win32.whl", hash = "sha256:aab75d99f3f2874733946a7648ce87a50019eb90baef931698f96b76b6769a46"}, - {file = "coverage-5.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:c2ff24df02a125b7b346c4c9078c8936da06964cc2d276292c357d64378158f8"}, - {file = "coverage-5.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:304fbe451698373dc6653772c72c5d5e883a4aadaf20343592a7abb2e643dae0"}, - {file = "coverage-5.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c96472b8ca5dc135fb0aa62f79b033f02aa434fb03a8b190600a5ae4102df1fd"}, - {file = "coverage-5.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8505e614c983834239f865da2dd336dcf9d72776b951d5dfa5ac36b987726e1b"}, - {file = "coverage-5.2.1-cp38-cp38-win32.whl", hash = "sha256:700997b77cfab016533b3e7dbc03b71d33ee4df1d79f2463a318ca0263fc29dd"}, - {file = "coverage-5.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:46794c815e56f1431c66d81943fa90721bb858375fb36e5903697d5eef88627d"}, - {file = "coverage-5.2.1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:16042dc7f8e632e0dcd5206a5095ebd18cb1d005f4c89694f7f8aafd96dd43a3"}, - {file = "coverage-5.2.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c1bbb628ed5192124889b51204de27c575b3ffc05a5a91307e7640eff1d48da4"}, - {file = "coverage-5.2.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4f6428b55d2916a69f8d6453e48a505c07b2245653b0aa9f0dee38785939f5e4"}, - {file = "coverage-5.2.1-cp39-cp39-win32.whl", hash = "sha256:9e536783a5acee79a9b308be97d3952b662748c4037b6a24cbb339dc7ed8eb89"}, - {file = "coverage-5.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:b8f58c7db64d8f27078cbf2a4391af6aa4e4767cc08b37555c4ae064b8558d9b"}, - {file = "coverage-5.2.1.tar.gz", hash = "sha256:a34cb28e0747ea15e82d13e14de606747e9e484fb28d63c999483f5d5188e89b"}, + {file = "coverage-5.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270"}, + {file = "coverage-5.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4"}, + {file = "coverage-5.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9"}, + {file = "coverage-5.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729"}, + {file = "coverage-5.3-cp27-cp27m-win32.whl", hash = "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d"}, + {file = "coverage-5.3-cp27-cp27m-win_amd64.whl", hash = "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418"}, + {file = "coverage-5.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9"}, + {file = "coverage-5.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5"}, + {file = "coverage-5.3-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822"}, + {file = "coverage-5.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097"}, + {file = "coverage-5.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9"}, + {file = "coverage-5.3-cp35-cp35m-win32.whl", hash = "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636"}, + {file = "coverage-5.3-cp35-cp35m-win_amd64.whl", hash = "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f"}, + {file = "coverage-5.3-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237"}, + {file = "coverage-5.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54"}, + {file = "coverage-5.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7"}, + {file = "coverage-5.3-cp36-cp36m-win32.whl", hash = "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a"}, + {file = "coverage-5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d"}, + {file = "coverage-5.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8"}, + {file = "coverage-5.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f"}, + {file = "coverage-5.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c"}, + {file = "coverage-5.3-cp37-cp37m-win32.whl", hash = "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751"}, + {file = "coverage-5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709"}, + {file = "coverage-5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516"}, + {file = "coverage-5.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f"}, + {file = "coverage-5.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259"}, + {file = "coverage-5.3-cp38-cp38-win32.whl", hash = "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82"}, + {file = "coverage-5.3-cp38-cp38-win_amd64.whl", hash = "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221"}, + {file = "coverage-5.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978"}, + {file = "coverage-5.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21"}, + {file = "coverage-5.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24"}, + {file = "coverage-5.3-cp39-cp39-win32.whl", hash = "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7"}, + {file = "coverage-5.3-cp39-cp39-win_amd64.whl", hash = "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7"}, + {file = "coverage-5.3.tar.gz", hash = "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0"}, ] cryptography = [ {file = "cryptography-3.1-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:969ae512a250f869c1738ca63be843488ff5cc031987d302c1f59c7dbe1b225f"}, @@ -1825,8 +1843,8 @@ factory-boy = [ {file = "factory_boy-2.12.0.tar.gz", hash = "sha256:faf48d608a1735f0d0a3c9cbf536d64f9132b547dae7ba452c4d99a79e84a370"}, ] faker = [ - {file = "Faker-4.1.2-py3-none-any.whl", hash = "sha256:bc4b8c908dfcd84e4fe5d9fa2e52fbe17546515fb8f126909b98c47badf05658"}, - {file = "Faker-4.1.2.tar.gz", hash = "sha256:ff188c416864e3f7d8becd8f9ee683a4b4101a2a2d2bcdcb3e84bb1bdd06eaae"}, + {file = "Faker-4.1.3-py3-none-any.whl", hash = "sha256:80bab8d46035a7393de827210c5d39c17109d3346d131946bde622137120c496"}, + {file = "Faker-4.1.3.tar.gz", hash = "sha256:075a95ac4c95765370919d787dcd958acfaea635005ad5af4d926cb0973800db"}, ] flake8 = [ {file = "flake8-3.8.3-py2.py3-none-any.whl", hash = "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c"}, @@ -1836,6 +1854,9 @@ flake8-polyfill = [ {file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"}, {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"}, ] +flake8-quotes = [ + {file = "flake8-quotes-3.2.0.tar.gz", hash = "sha256:3f1116e985ef437c130431ac92f9b3155f8f652fda7405ac22ffdfd7a9d1055e"}, +] future = [ {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, ] @@ -1978,32 +1999,32 @@ nbformat = [ {file = "nbformat-5.0.7.tar.gz", hash = "sha256:54d4d6354835a936bad7e8182dcd003ca3dc0cedfee5a306090e04854343b340"}, ] numpy = [ - {file = "numpy-1.19.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1cca51512299841bf69add3b75361779962f9cee7d9ee3bb446d5982e925b69"}, - {file = "numpy-1.19.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c9591886fc9cbe5532d5df85cb8e0cc3b44ba8ce4367bd4cf1b93dc19713da72"}, - {file = "numpy-1.19.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:cf1347450c0b7644ea142712619533553f02ef23f92f781312f6a3553d031fc7"}, - {file = "numpy-1.19.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:ed8a311493cf5480a2ebc597d1e177231984c818a86875126cfd004241a73c3e"}, - {file = "numpy-1.19.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3673c8b2b29077f1b7b3a848794f8e11f401ba0b71c49fbd26fb40b71788b132"}, - {file = "numpy-1.19.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:56ef7f56470c24bb67fb43dae442e946a6ce172f97c69f8d067ff8550cf782ff"}, - {file = "numpy-1.19.1-cp36-cp36m-win32.whl", hash = "sha256:aaf42a04b472d12515debc621c31cf16c215e332242e7a9f56403d814c744624"}, - {file = "numpy-1.19.1-cp36-cp36m-win_amd64.whl", hash = "sha256:082f8d4dd69b6b688f64f509b91d482362124986d98dc7dc5f5e9f9b9c3bb983"}, - {file = "numpy-1.19.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e4f6d3c53911a9d103d8ec9518190e52a8b945bab021745af4939cfc7c0d4a9e"}, - {file = "numpy-1.19.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:5b6885c12784a27e957294b60f97e8b5b4174c7504665333c5e94fbf41ae5d6a"}, - {file = "numpy-1.19.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1bc0145999e8cb8aed9d4e65dd8b139adf1919e521177f198529687dbf613065"}, - {file = "numpy-1.19.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:5a936fd51049541d86ccdeef2833cc89a18e4d3808fe58a8abeb802665c5af93"}, - {file = "numpy-1.19.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:ef71a1d4fd4858596ae80ad1ec76404ad29701f8ca7cdcebc50300178db14dfc"}, - {file = "numpy-1.19.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b9792b0ac0130b277536ab8944e7b754c69560dac0415dd4b2dbd16b902c8954"}, - {file = "numpy-1.19.1-cp37-cp37m-win32.whl", hash = "sha256:b12e639378c741add21fbffd16ba5ad25c0a1a17cf2b6fe4288feeb65144f35b"}, - {file = "numpy-1.19.1-cp37-cp37m-win_amd64.whl", hash = "sha256:8343bf67c72e09cfabfab55ad4a43ce3f6bf6e6ced7acf70f45ded9ebb425055"}, - {file = "numpy-1.19.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e45f8e981a0ab47103181773cc0a54e650b2aef8c7b6cd07405d0fa8d869444a"}, - {file = "numpy-1.19.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:667c07063940e934287993366ad5f56766bc009017b4a0fe91dbd07960d0aba7"}, - {file = "numpy-1.19.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:480fdd4dbda4dd6b638d3863da3be82873bba6d32d1fc12ea1b8486ac7b8d129"}, - {file = "numpy-1.19.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:935c27ae2760c21cd7354402546f6be21d3d0c806fffe967f745d5f2de5005a7"}, - {file = "numpy-1.19.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:309cbcfaa103fc9a33ec16d2d62569d541b79f828c382556ff072442226d1968"}, - {file = "numpy-1.19.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7ed448ff4eaffeb01094959b19cbaf998ecdee9ef9932381420d514e446601cd"}, - {file = "numpy-1.19.1-cp38-cp38-win32.whl", hash = "sha256:de8b4a9b56255797cbddb93281ed92acbc510fb7b15df3f01bd28f46ebc4edae"}, - {file = "numpy-1.19.1-cp38-cp38-win_amd64.whl", hash = "sha256:92feb989b47f83ebef246adabc7ff3b9a59ac30601c3f6819f8913458610bdcc"}, - {file = "numpy-1.19.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:e1b1dc0372f530f26a03578ac75d5e51b3868b9b76cd2facba4c9ee0eb252ab1"}, - {file = "numpy-1.19.1.zip", hash = "sha256:b8456987b637232602ceb4d663cb34106f7eb780e247d51a260b84760fd8f491"}, + {file = "numpy-1.19.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b594f76771bc7fc8a044c5ba303427ee67c17a09b36e1fa32bde82f5c419d17a"}, + {file = "numpy-1.19.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:e6ddbdc5113628f15de7e4911c02aed74a4ccff531842c583e5032f6e5a179bd"}, + {file = "numpy-1.19.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:3733640466733441295b0d6d3dcbf8e1ffa7e897d4d82903169529fd3386919a"}, + {file = "numpy-1.19.2-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:4339741994c775396e1a274dba3609c69ab0f16056c1077f18979bec2a2c2e6e"}, + {file = "numpy-1.19.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c6646314291d8f5ea900a7ea9c4261f834b5b62159ba2abe3836f4fa6705526"}, + {file = "numpy-1.19.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:7118f0a9f2f617f921ec7d278d981244ba83c85eea197be7c5a4f84af80a9c3c"}, + {file = "numpy-1.19.2-cp36-cp36m-win32.whl", hash = "sha256:9a3001248b9231ed73894c773142658bab914645261275f675d86c290c37f66d"}, + {file = "numpy-1.19.2-cp36-cp36m-win_amd64.whl", hash = "sha256:967c92435f0b3ba37a4257c48b8715b76741410467e2bdb1097e8391fccfae15"}, + {file = "numpy-1.19.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d526fa58ae4aead839161535d59ea9565863bb0b0bdb3cc63214613fb16aced4"}, + {file = "numpy-1.19.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:eb25c381d168daf351147713f49c626030dcff7a393d5caa62515d415a6071d8"}, + {file = "numpy-1.19.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:62139af94728d22350a571b7c82795b9d59be77fc162414ada6c8b6a10ef5d02"}, + {file = "numpy-1.19.2-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:0c66da1d202c52051625e55a249da35b31f65a81cb56e4c69af0dfb8fb0125bf"}, + {file = "numpy-1.19.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:2117536e968abb7357d34d754e3733b0d7113d4c9f1d921f21a3d96dec5ff716"}, + {file = "numpy-1.19.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:54045b198aebf41bf6bf4088012777c1d11703bf74461d70cd350c0af2182e45"}, + {file = "numpy-1.19.2-cp37-cp37m-win32.whl", hash = "sha256:aba1d5daf1144b956bc87ffb87966791f5e9f3e1f6fab3d7f581db1f5b598f7a"}, + {file = "numpy-1.19.2-cp37-cp37m-win_amd64.whl", hash = "sha256:addaa551b298052c16885fc70408d3848d4e2e7352de4e7a1e13e691abc734c1"}, + {file = "numpy-1.19.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:58d66a6b3b55178a1f8a5fe98df26ace76260a70de694d99577ddeab7eaa9a9d"}, + {file = "numpy-1.19.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:59f3d687faea7a4f7f93bd9665e5b102f32f3fa28514f15b126f099b7997203d"}, + {file = "numpy-1.19.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cebd4f4e64cfe87f2039e4725781f6326a61f095bc77b3716502bed812b385a9"}, + {file = "numpy-1.19.2-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c35a01777f81e7333bcf276b605f39c872e28295441c265cd0c860f4b40148c1"}, + {file = "numpy-1.19.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d7ac33585e1f09e7345aa902c281bd777fdb792432d27fca857f39b70e5dd31c"}, + {file = "numpy-1.19.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:04c7d4ebc5ff93d9822075ddb1751ff392a4375e5885299445fcebf877f179d5"}, + {file = "numpy-1.19.2-cp38-cp38-win32.whl", hash = "sha256:51ee93e1fac3fe08ef54ff1c7f329db64d8a9c5557e6c8e908be9497ac76374b"}, + {file = "numpy-1.19.2-cp38-cp38-win_amd64.whl", hash = "sha256:1669ec8e42f169ff715a904c9b2105b6640f3f2a4c4c2cb4920ae8b2785dac65"}, + {file = "numpy-1.19.2-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:0bfd85053d1e9f60234f28f63d4a5147ada7f432943c113a11afcf3e65d9d4c8"}, + {file = "numpy-1.19.2.zip", hash = "sha256:0d310730e1e793527065ad7dde736197b705d0e4c9999775f212b03c44a8484c"}, ] packaging = [ {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, @@ -2061,8 +2082,8 @@ pyflakes = [ {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, ] pygments = [ - {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"}, - {file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"}, + {file = "Pygments-2.7.1-py3-none-any.whl", hash = "sha256:307543fe65c0947b126e83dd5a61bd8acbd84abec11f43caebaf5534cbc17998"}, + {file = "Pygments-2.7.1.tar.gz", hash = "sha256:926c3f319eda178d1bd90851e4317e6d8cdb5e292a3386aac9bd75eca29cf9c7"}, ] pymbolic = [ {file = "pymbolic-2020.1.tar.gz", hash = "sha256:ca029399f9480f6d51fbac0349fddbb42d937620deb03befa0ba94ac08895e6b"}, @@ -2094,7 +2115,7 @@ pyparsing = [ {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pyrsistent = [ - {file = "pyrsistent-0.16.0.tar.gz", hash = "sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3"}, + {file = "pyrsistent-0.17.3.tar.gz", hash = "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"}, ] pysaml2 = [ {file = "pysaml2-5.4.0-py2.py3-none-any.whl", hash = "sha256:24dedd1a5d15196c913543a9202cfd5d13097c46c2e021b281278f962a780bdb"}, @@ -2109,8 +2130,8 @@ pytest-cov = [ {file = "pytest_cov-2.10.1-py2.py3-none-any.whl", hash = "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191"}, ] pytest-django = [ - {file = "pytest-django-3.9.0.tar.gz", hash = "sha256:664e5f42242e5e182519388f01b9f25d824a9feb7cd17d8f863c8d776f38baf9"}, - {file = "pytest_django-3.9.0-py2.py3-none-any.whl", hash = "sha256:64f99d565dd9497af412fcab2989fe40982c1282d4118ff422b407f3f7275ca5"}, + {file = "pytest-django-3.10.0.tar.gz", hash = "sha256:4de6dbd077ed8606616958f77655fed0d5e3ee45159475671c7fa67596c6dba6"}, + {file = "pytest_django-3.10.0-py2.py3-none-any.whl", hash = "sha256:c33e3d3da14d8409b125d825d4e74da17bb252191bf6fc3da6856e27a8b73ea4"}, ] pytest-factoryboy = [ {file = "pytest-factoryboy-2.0.3.tar.gz", hash = "sha256:ffef3fb7ddec1299d3df0d334846259023f3d1da5ab887ad880139a8253a5a1a"}, diff --git a/pyproject.toml b/pyproject.toml index eb46e7a7ca9d6f5485e05dcf8147b4e729b2348c..d29c4009c763ecc5b4b8029047b0fbb1912717f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -105,6 +105,7 @@ colorama = "*" [tool.poetry.dev-dependencies] codecov = "^2.1.4" factory_boy = "^2.12.0" +flake8-quotes = "^3.2.0" flake8 = "^3.8.3" pep8-naming = "^0.10.0" mypy = "^0.780" diff --git a/relate/bin/relate.py b/relate/bin/relate.py index 6be984ec3234b70e7cce6e564b646f63d84bd71c..138109a6693738ee8b668705e4ed4c60b18e4118 100644 --- a/relate/bin/relate.py +++ b/relate/bin/relate.py @@ -220,23 +220,23 @@ def main() -> None: os.environ["RELATE_COMMAND_LINE"] = "1" parser = argparse.ArgumentParser( - description='RELATE course content command line tool') + description="RELATE course content command line tool") subp = parser.add_subparsers() parser_validate = subp.add_parser("validate") parser_validate.add_argument("--course-file", default="course.yml") parser_validate.add_argument("--events-file", default="events.yml") - parser_validate.add_argument('REPO_ROOT', default=os.getcwd()) + parser_validate.add_argument("REPO_ROOT", default=os.getcwd()) parser_validate.set_defaults(func=validate) parser_test_code = subp.add_parser("test-code") - parser_test_code.add_argument('--repo-root', default=os.getcwd()) - parser_test_code.add_argument('FLOW_OR_PROBLEM_YMLS', nargs="+") + parser_test_code.add_argument("--repo-root", default=os.getcwd()) + parser_test_code.add_argument("FLOW_OR_PROBLEM_YMLS", nargs="+") parser_test_code.set_defaults(func=test_code) parser_expand_yaml = subp.add_parser("expand-yaml") - parser_expand_yaml.add_argument('--repo-root', default=os.getcwd()) - parser_expand_yaml.add_argument('YAML_FILE') + parser_expand_yaml.add_argument("--repo-root", default=os.getcwd()) + parser_expand_yaml.add_argument("YAML_FILE") parser_expand_yaml.set_defaults(func=expand_yaml_ui) args = parser.parse_args() diff --git a/relate/celery.py b/relate/celery.py index fad895f5f1f583012b590f87a08983ec0bc4a2e3..abd3beb8778f3f2d8e82b98b974c525a80267776 100644 --- a/relate/celery.py +++ b/relate/celery.py @@ -5,18 +5,18 @@ import os from celery import Celery # set the default Django settings module for the 'celery' program. -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'relate.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "relate.settings") from django.conf import settings -app = Celery('relate') +app = Celery("relate") # Using a string here means the worker will not have to # pickle the object when using Windows. -app.config_from_object('django.conf:settings') +app.config_from_object("django.conf:settings") app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) @app.task(bind=True) def debug_task(self): # pragma: no cover - print('Request: {0!r}'.format(self.request)) + print("Request: {0!r}".format(self.request)) diff --git a/relate/settings.py b/relate/settings.py index 497dabb0332282b5edd48d686963fd9643e6525f..81d827cd4f24d99a858a7e0bdfe4a8b0f524f196 100644 --- a/relate/settings.py +++ b/relate/settings.py @@ -96,10 +96,10 @@ AUTHENTICATION_BACKENDS = ( if local_settings.get("RELATE_SIGN_IN_BY_SAML2_ENABLED"): AUTHENTICATION_BACKENDS = AUTHENTICATION_BACKENDS + ( # type: ignore - 'djangosaml2.backends.Saml2Backend', + "djangosaml2.backends.Saml2Backend", ) -AUTH_USER_MODEL = 'accounts.User' +AUTH_USER_MODEL = "accounts.User" # }}} @@ -113,11 +113,11 @@ CODEMIRROR_PATH = "codemirror" # }}} -ROOT_URLCONF = 'relate.urls' +ROOT_URLCONF = "relate.urls" CRISPY_FAIL_SILENTLY = False -WSGI_APPLICATION = 'relate.wsgi.application' +WSGI_APPLICATION = "relate.wsgi.application" # {{{ context processors @@ -170,9 +170,9 @@ if RELATE_OVERRIDE_TEMPLATES_DIRS: # default, likely overriden by local_settings.py DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "db.sqlite3"), } } @@ -180,7 +180,7 @@ DATABASES = { # {{{ internationalization -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" USE_I18N = True @@ -205,18 +205,18 @@ STATICFILES_DIRS = ( join(BASE_DIR, "relate", "static"), ) -STATIC_URL = '/static/' +STATIC_URL = "/static/" STATIC_ROOT = join(BASE_DIR, "static") -# local select2 'static' resources instead of from CDN +# local select2 "static" resources instead of from CDN # https://goo.gl/dY6xf7 -SELECT2_JS = 'select2/dist/js/select2.min.js' -SELECT2_CSS = 'select2/dist/css/select2.css' +SELECT2_JS = "select2/dist/js/select2.min.js" +SELECT2_CSS = "select2/dist/css/select2.css" # }}} -SESSION_COOKIE_NAME = 'relate_sessionid' +SESSION_COOKIE_NAME = "relate_sessionid" SESSION_COOKIE_AGE = 12096000 # 20 weeks # {{{ app defaults @@ -254,13 +254,13 @@ if "CELERY_BROKER_URL" not in globals(): "If there is no queue server installed, long-running tasks will " "appear to hang.") - CELERY_BROKER_URL = 'amqp://' + CELERY_BROKER_URL = "amqp://" -CELERY_ACCEPT_CONTENT = ['pickle', 'json'] -CELERY_TASK_SERIALIZER = 'pickle' +CELERY_ACCEPT_CONTENT = ["pickle", "json"] +CELERY_TASK_SERIALIZER = "pickle" # (pickle is buggy in django-celery-results 1.0.1) # https://github.com/celery/django-celery-results/issues/50 -CELERY_RESULT_SERIALIZER = 'json' +CELERY_RESULT_SERIALIZER = "json" CELERY_TRACK_STARTED = True if "CELERY_RESULT_BACKEND" not in globals(): @@ -275,22 +275,22 @@ if "CELERY_RESULT_BACKEND" not in globals(): # transaction. But if we're using the in-memory cache, using # cache as a results backend doesn't make much sense. - CELERY_RESULT_BACKEND = 'django-cache' + CELERY_RESULT_BACKEND = "django-cache" else: - CELERY_RESULT_BACKEND = 'django-db' + CELERY_RESULT_BACKEND = "django-db" # }}} LOCALE_PATHS = ( - BASE_DIR + '/locale', + BASE_DIR + "/locale", ) # {{{ saml2 # This makes SAML2 logins compatible with (and usable at the same time as) # email-based logins. -SAML_DJANGO_USER_MAIN_ATTRIBUTE = 'username' +SAML_DJANGO_USER_MAIN_ATTRIBUTE = "username" SAML_CREATE_UNKNOWN_USER = True diff --git a/relate/urls.py b/relate/urls.py index 4b63ecdda5f85aadb8a5b71f8f0198cb5d6e4668..ec29c0181f39f1934906c9702d10a440641c3ef2 100644 --- a/relate/urls.py +++ b/relate/urls.py @@ -100,18 +100,18 @@ urlpatterns = [ # {{{ troubleshooting - url(r'^user/impersonate/$', + url(r"^user/impersonate/$", course.auth.impersonate, name="relate-impersonate"), - url(r'^user/stop_impersonating/$', + url(r"^user/stop_impersonating/$", course.auth.stop_impersonating, name="relate-stop_impersonating"), - url(r'^time/set-fake-time/$', + url(r"^time/set-fake-time/$", course.views.set_fake_time, name="relate-set_fake_time"), - url(r'^time/set-pretend-facilities/$', + url(r"^time/set-pretend-facilities/$", course.views.set_pretend_facilities, name="relate-set_pretend_facilities"), @@ -119,7 +119,7 @@ urlpatterns = [ # {{{ course - url(r'^$', course.views.home, name='relate-home'), + url(r"^$", course.views.home, name="relate-home"), url(r"^course" "/" + COURSE_ID_REGEX @@ -543,7 +543,7 @@ urlpatterns = [ # {{{ django-select2 - url(r'^select2/', include('django_select2.urls')), + url(r"^select2/", include("django_select2.urls")), #}}} @@ -559,18 +559,18 @@ urlpatterns = [ course.api.get_flow_session_content, name="relate-course_get_flow_session_content"), - url(r'^admin/', admin.site.urls), + url(r"^admin/", admin.site.urls), ] if settings.RELATE_SIGN_IN_BY_SAML2_ENABLED: urlpatterns.extend([ - url(r'^saml2/', include('djangosaml2.urls')), + url(r"^saml2/", include("djangosaml2.urls")), ]) if settings.DEBUG: # pragma: no cover import djangosaml2.views urlpatterns.extend([ # Keep commented unless debugging SAML2. - url(r'^saml2-test/', djangosaml2.views.echo_attributes), + url(r"^saml2-test/", djangosaml2.views.echo_attributes), ]) # vim: fdm=marker diff --git a/relate/utils.py b/relate/utils.py index 2715476392ca985f9f9675c1f742fcbe165a457e..c9247c6981f016793c41f27d007b9f838febe6ec 100644 --- a/relate/utils.py +++ b/relate/utils.py @@ -138,7 +138,7 @@ def is_maintenance_mode(request): import ipaddress remote_address = ipaddress.ip_address( - str(request.META['REMOTE_ADDR'])) + str(request.META["REMOTE_ADDR"])) for exc in exceptions: if remote_address in ipaddress.ip_network(str(exc)): @@ -226,7 +226,7 @@ def local_now(): return tz.localize(datetime.datetime.now()) # type: ignore -def format_datetime_local(datetime, format='DATETIME_FORMAT'): +def format_datetime_local(datetime, format="DATETIME_FORMAT"): # type: (datetime.datetime, str) -> str """ Format a datetime object to a localized string via python. @@ -371,10 +371,10 @@ def get_outbound_mail_connection(label=None, **kwargs): # type: (Optional[Text], **Any) -> Any from django.conf import settings if label is None: - label = getattr(settings, 'EMAIL_CONNECTION_DEFAULT', None) + label = getattr(settings, "EMAIL_CONNECTION_DEFAULT", None) try: - connections = getattr(settings, 'EMAIL_CONNECTIONS') + connections = getattr(settings, "EMAIL_CONNECTIONS") options = connections[label] except (KeyError, AttributeError): # Neither EMAIL_CONNECTIONS nor @@ -440,7 +440,7 @@ def force_remove_path(path): import shutil def remove_readonly(func, path, _): # noqa - "Clear the readonly bit and reattempt the removal" + """Clear the readonly bit and reattempt the removal""" os.chmod(path, stat.S_IWRITE) func(path) diff --git a/setup.cfg b/setup.cfg index f0ec8800bab2c6fca6bb0ef756752230ab8ab9e1..b7c68d694104adc621aba03d5be7171e82673b54 100644 --- a/setup.cfg +++ b/setup.cfg @@ -10,6 +10,10 @@ ignore = E126,E127,E128,E123,E226,E241,E242,E265,E402,W503 max-line-length=85 exclude=course/migrations,accounts/migrations,static,components,course/mdx_mathjax.py,saml-config/attribute-maps,doc/conf.py,local_settings.py +inline-quotes = " +docstring-quotes = """ +multiline-quotes = """ + [mypy] strict_optional = True ignore_missing_imports = True diff --git a/tests/base_test_mixins.py b/tests/base_test_mixins.py index 54b0235fcf7904583ecd1371263dcf185443ebaa..8d75ecfd15e41ee34006225bd46d92a93f3641e9 100644 --- a/tests/base_test_mixins.py +++ b/tests/base_test_mixins.py @@ -2426,7 +2426,7 @@ class MockAddMessageMixing(object): if not_called: fail_msg = "%s unexpectedly not added in messages. " % repr(not_called) if joined_msgs: - fail_msg += "the actual message are \"%s\"" % joined_msgs + fail_msg += 'the actual message are "%s"' % joined_msgs self.fail(fail_msg) if reset: self._mock_add_message.reset_mock() @@ -2444,7 +2444,7 @@ class MockAddMessageMixing(object): if called: fail_msg = "%s unexpectedly added in messages. " % repr(called) - fail_msg += "the actual message are \"%s\"" % joined_msgs + fail_msg += 'the actual message are \"%s\"' % joined_msgs self.fail(fail_msg) if reset: self._mock_add_message.reset_mock() diff --git a/tests/test_content.py b/tests/test_content.py index bc19cf0ab90eb2cb7aba641552f95c586f1f190e..571a5c1d40a4e45c51ab7d2ee9886cda68581492 100644 --- a/tests/test_content.py +++ b/tests/test_content.py @@ -172,7 +172,7 @@ TEST_IPYNB_BYTES = json.dumps({ ], "source": [ "def function1():\n", - " print(\"This is function1\")\n", + ' print("This is function1")\n', "\n", "function1()" ] @@ -193,7 +193,7 @@ TEST_IPYNB_BYTES = json.dumps({ "outputs": [], "source": [ "def function2():\n", - " print(\"This is function2\")" + ' print("This is function2")' ] }, { @@ -738,8 +738,8 @@ class AttrToStringTest(unittest.TestCase): def test(self): self.assertEqual(content._attr_to_string("disabled", None), "disabled") self.assertEqual(content._attr_to_string( - "id", "\"abc\""), "id='\"abc\"'") - self.assertEqual(content._attr_to_string("id", "abc"), "id=\"abc\"") + "id", '"abc"'), "id='\"abc\"'") + self.assertEqual(content._attr_to_string("id", "abc"), 'id="abc"') MARKDOWN_WITH_LINK_FRAGMENT = """ @@ -755,17 +755,17 @@ content: |
    [A static page](course:test#abcd) - + ## link to another course [A static page](course:another-course) - + ## calendar linkes [A static page](calendar:) - + ## images ![alt text](https://raw.githubusercontent.com/inducer/relate/master/doc/images/screenshot.png "Example") - + ## object data