From 9f4a702a1151abd96fa4d25c6757317865e969f7 Mon Sep 17 00:00:00 2001 From: Mark Walker Date: Fri, 24 Nov 2023 13:08:39 +0000 Subject: [PATCH 1/6] feat: Add ruff and fix issues --- .github/workflows/flake8.yml | 15 --- .github/workflows/ruff.yml | 69 ++++++++++++ .pre-commit-config.yaml | 13 +-- applications/admin.py | 2 +- applications/forms.py | 6 +- applications/models.py | 17 ++- applications/services.py | 2 +- applications/views.py | 3 +- coach/models.py | 21 ++-- core/admin/event.py | 15 ++- core/admin/filters/event.py | 2 +- core/context_processors.py | 1 - core/default_eventpage_content.py | 2 +- core/deploy_event.py | 6 +- .../commands/backup_postgres_to_s3.py | 8 +- core/management/commands/handle_emails.py | 2 +- core/management_utils.py | 2 +- core/models/event.py | 30 +++--- core/quotes.py | 6 +- core/templatetags/core_tags.py | 4 +- core/views.py | 10 +- djangogirls/settings.py | 5 +- organize/forms.py | 6 +- organize/models.py | 14 +-- patreonmanager/admin.py | 3 +- patreonmanager/filters.py | 2 +- patreonmanager/models.py | 6 ++ patreonmanager/utils/download.py | 2 +- pyproject.toml | 101 +++++++++++++++--- sponsor/models.py | 12 +-- story/management/commands/fetch_stories.py | 8 +- .../commands/fetch_tumblr_stories.py | 3 +- story/models.py | 8 +- .../applications/models/test_applications.py | 6 +- .../views/test_applications_download.py | 8 +- tests/applications/views/test_apply.py | 34 +++--- tests/conftest.py | 6 +- tests/contact/test_views.py | 2 +- tests/core/test_admin.py | 7 +- tests/core/test_gmail_accounts.py | 2 +- tests/core/test_utils.py | 4 +- tests/core/test_views.py | 4 +- 42 files changed, 297 insertions(+), 182 deletions(-) delete mode 100644 .github/workflows/flake8.yml create mode 100644 .github/workflows/ruff.yml diff --git a/.github/workflows/flake8.yml b/.github/workflows/flake8.yml deleted file mode 100644 index 1301b2240..000000000 --- a/.github/workflows/flake8.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: flake8 - -on: - pull_request: - push: - -jobs: - pre-commit: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - - uses: pre-commit/action@v2.0.3 - with: - extra_args: flake8 --all-files diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml new file mode 100644 index 000000000..5eb779739 --- /dev/null +++ b/.github/workflows/ruff.yml @@ -0,0 +1,69 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +jobs: + ruff: + name: ruff + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + cache: 'pip' + - run: | + python -m pip install --upgrade pip + pip install ruff + - name: Run Ruff + run: ruff . + + npm-production-test: + name: Frontend build test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Cache node modules + id: cache-npm + uses: actions/cache@v3 + env: + cache-name: cache-node-modules + with: + # npm cache files are stored in `~/.npm` on Linux/macOS + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + - name: Use Node.js 18.5.0 + uses: actions/setup-node@v4 + with: + node-version: 18.5.0 + - name: npm install + env: + NODE_ENV: production + run: npm ci + working-directory: ./static + - name: npm gulp + env: + NODE_ENV: production + run: npm run gulp + working-directory: ./static + - name: npm build + env: + NODE_ENV: production + run: npm run build + working-directory: ./static diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 285870bea..99de6d059 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,19 +35,14 @@ repos: "--ignore-init-module-imports", ] files: \.py$ - - repo: https://github.com/pycqa/isort - rev: 5.12.0 - hooks: - - id: isort - name: isort - entry: bash -c 'isort "$@"; git add -u;' -- - repo: https://github.com/psf/black rev: 23.3.0 hooks: - id: black entry: bash -c 'black "$@"; git add -u;' -- language_version: python3.10 - - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: "v0.0.280" hooks: - - id: flake8 + - id: ruff + args: [--fix, --exit-non-zero-on-fix] diff --git a/applications/admin.py b/applications/admin.py index d029f31c9..5b39a7d25 100644 --- a/applications/admin.py +++ b/applications/admin.py @@ -57,7 +57,7 @@ def lookups(self, request, queryset): qs = Form.objects.all() if not request.user.is_superuser: qs = qs.filter(event__team__in=[request.user]) - return map(lambda x: (x.id, str(x)), qs) + return map(lambda x: (x.id, str(x)), qs) # noqa: C417 def queryset(self, request, queryset): if self.value(): diff --git a/applications/forms.py b/applications/forms.py index 97b7def27..8801f03a7 100644 --- a/applications/forms.py +++ b/applications/forms.py @@ -61,7 +61,7 @@ def save(self, *args, **kwargs): else: application.newsletter_optin = False - value = ", ".join(value) if type(value) == list else value + value = ", ".join(value) if isinstance(value, list) else value if question: answers.append(Answer(question=question, answer=value)) @@ -96,9 +96,9 @@ def save(self, *args, **kwargs): ], ) msg.content_subtype = "html" - try: + try: # noqa: SIM105 msg.send() - except: + except: # noqa: E722 # TODO: what should we do when sending fails? pass diff --git a/applications/models.py b/applications/models.py index b6c6c8583..7aaae92ee 100644 --- a/applications/models.py +++ b/applications/models.py @@ -53,7 +53,7 @@ def __str__(self): return f"Application form for {self.event.name}" def save(self, *args, **kwargs): - is_form_new = False if self.pk else True + is_form_new = not self.pk super().save(*args, **kwargs) if is_form_new: @@ -159,6 +159,9 @@ class Meta: ) ] + def __str__(self): + return str(self.pk) + def save(self, *args, **kwargs): if self.pk is None: current_max = Application.objects.filter(form=self.form).aggregate(models.Max("number"))["number__max"] @@ -231,9 +234,6 @@ def is_scored_by_user(self, user): """ return self.scores.filter(user=user, score__gt=0).exists() - def __str__(self): - return str(self.pk) - class Answer(models.Model): application = models.ForeignKey(Application, null=False, blank=False, on_delete=models.deletion.PROTECT) @@ -243,6 +243,9 @@ class Answer(models.Model): class Meta: ordering = ("question__order",) + def __str__(self): + return f"{self.application} - {self.question}" + class Score(models.Model): """ @@ -264,6 +267,9 @@ class Meta: "application", ) + def __str__(self): + return f"{self.user} - {self.application}. Score {self.score}" + class Email(models.Model): form = models.ForeignKey(Form, on_delete=models.deletion.PROTECT) @@ -326,10 +332,11 @@ def send(self): msg = EmailMessage(self.subject, body, self.sent_from, [recipient.email]) msg.content_subtype = "html" + # TODO: What's the possible exception here? Catch specifics try: msg.send() successfuly_sent.append(recipient.email) - except: # TODO: What's the possible exception here? Catch specifics + except: # noqa: E722 failed_to_sent.append(recipient.email) self.sent = timezone.now() diff --git a/applications/services.py b/applications/services.py index 0983c2111..440126b37 100644 --- a/applications/services.py +++ b/applications/services.py @@ -37,7 +37,7 @@ def get_applications_for_event(event, state=None, rsvp_status=None, order=None, applications = applications.filter(state__in=state) if order: - is_reversed = True if order[0] == "-" else False + is_reversed = order[0] == "-" order = order[1:] if order[0] == "-" else order if order == "average_score": # here is an exception for the average_score, because we also want to get diff --git a/applications/views.py b/applications/views.py index 8a57d1bb4..def18a0a8 100644 --- a/applications/views.py +++ b/applications/views.py @@ -109,9 +109,10 @@ def applications_csv(request, page_url): rsvp_status = request.GET.getlist("rsvp_status", None) event = get_event(page_url, request.user.is_authenticated, False) order = request.GET.get("order", None) + # TODO: what's the exception here? try: applications = get_applications_for_event(event, state, rsvp_status, order) - except: # TODO: what's the exception here? + except: # noqa: E722 return redirect("core:event", page_url=page_url) response = HttpResponse(content_type="text/csv") response["Content-Disposition"] = f'attachment; filename="{page_url}.csv"' diff --git a/coach/models.py b/coach/models.py index 25f704e20..611c7d669 100644 --- a/coach/models.py +++ b/coach/models.py @@ -27,6 +27,18 @@ class Meta: def __str__(self): return self.name + def save(self, *args, **kwargs): + try: + super().save(*args, **kwargs) + except IntegrityError as err: + raise ValidationError( + { + "name": _("Coach with name %s and twitter_handle %s already exists.") + % (self.name, self.twitter_handle) + } + ) from err + return self + def photo_display_for_admin(self): coach_change_url = reverse("admin:coach_coach_change", args=[self.id]) return f""" @@ -45,12 +57,3 @@ def photo_url(self): return DEFAULT_COACH_PHOTO return DEFAULT_COACH_PHOTO - - def save(self, *args, **kwargs): - try: - super().save(*args, **kwargs) - except IntegrityError: - raise ValidationError( - {"name": _(f"Coach with name {self.name} and twitter_handle {self.twitter_handle} " "already exists.")} - ) - return self diff --git a/core/admin/event.py b/core/admin/event.py index c9f983021..5053f4311 100644 --- a/core/admin/event.py +++ b/core/admin/event.py @@ -96,7 +96,7 @@ def has_stats(self, obj): def full_url(self, obj): url = reverse("core:event", kwargs={"page_url": obj.page_url}) url = f"https://djangogirls.org{url}" - return mark_safe('{url}'.format(url=url)) + return mark_safe(f'{url}') def get_readonly_fields(self, request, obj=None): fields = set(self.readonly_fields) | {"full_url"} @@ -228,13 +228,12 @@ def view_manage_organizers(self, request): user = User.objects.get(id=request.GET["remove"]) if user == request.user: messages.error(request, _("You cannot remove yourself from a team.")) - else: - if user in event.team.all(): - event.team.remove(user) - messages.success( - request, _("Organizer %(user_name)s has been removed") % {"user_name": user.get_full_name()} - ) - return HttpResponseRedirect(reverse("admin:core_event_manage_organizers") + f"?event_id={event.id}") + elif user in event.team.all(): + event.team.remove(user) + messages.success( + request, _("Organizer %(user_name)s has been removed") % {"user_name": user.get_full_name()} + ) + return HttpResponseRedirect(reverse("admin:core_event_manage_organizers") + f"?event_id={event.id}") return render( request, diff --git a/core/admin/filters/event.py b/core/admin/filters/event.py index bad03544a..00a8ab8d1 100644 --- a/core/admin/filters/event.py +++ b/core/admin/filters/event.py @@ -11,7 +11,7 @@ def lookups(self, request, queryset): qs = Event.objects.all() if not request.user.is_superuser: qs = qs.filter(team__in=[request.user]) - return map(lambda x: (x.id, str(x)), qs) + return map(lambda x: (x.id, str(x)), qs) # noqa: C417 def queryset(self, request, queryset): if self.value(): diff --git a/core/context_processors.py b/core/context_processors.py index 534916a8e..8776cb734 100644 --- a/core/context_processors.py +++ b/core/context_processors.py @@ -27,7 +27,6 @@ def statistics(request): "attendees_sum": attendees["attendees"], "applicants_sum": attendees["applicants"], "organizers_count": organizers.count(), - "bronze": bronze, "diamond": diamond, "gold": gold, "platinum": platinum, diff --git a/core/default_eventpage_content.py b/core/default_eventpage_content.py index 41afd8136..e7cc105bc 100644 --- a/core/default_eventpage_content.py +++ b/core/default_eventpage_content.py @@ -27,7 +27,7 @@ def get_random_photo(section): if section in DEFAULT_BACKGROUND_PHOTOS: photos = DEFAULT_BACKGROUND_PHOTOS[section] - return UploadedFile(open(photos[random.randint(0, len(photos) - 1)], "rb")) + return UploadedFile(open(photos[random.randint(0, len(photos) - 1)], "rb")) # noqa: SIM115 return None diff --git a/core/deploy_event.py b/core/deploy_event.py index a0a1a07c2..cd8b2ee07 100644 --- a/core/deploy_event.py +++ b/core/deploy_event.py @@ -13,10 +13,8 @@ def copy_event(previous_event, event_date): previous_event_id = previous_event.pk # If event is already Django Girls City #2, remove #2 from it - if "#" in previous_event.name: - generic_event_name = previous_event.name.split(" #")[0] - else: - generic_event_name = previous_event.name + + generic_event_name = previous_event.name.split(" #")[0] if "#" in previous_event.name else previous_event.name previous_event_name = f"{generic_event_name} #{number}" event_name = f"{generic_event_name} #{number + 1}" diff --git a/core/management/commands/backup_postgres_to_s3.py b/core/management/commands/backup_postgres_to_s3.py index 8af8d65a1..b3e387c77 100644 --- a/core/management/commands/backup_postgres_to_s3.py +++ b/core/management/commands/backup_postgres_to_s3.py @@ -10,10 +10,10 @@ class Command(BaseCommand): help = "Backs up PostgreSQL database to AWS S3" def handle(self, *args, **options): - AWS_ACCESS_KEY_ID = os.environ.get("AWS_S3_ACCESS_KEY_ID") - AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_S3_SECRET_ACCESS_KEY") - AWS_REGION_NAME = os.environ.get("AWS_S3_REGION_NAME") - AWS_BUCKET_NAME = os.environ.get("AWS_S3_BUCKET_NAME") + AWS_ACCESS_KEY_ID = os.environ.get("AWS_S3_ACCESS_KEY_ID") # noqa: N806 + AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_S3_SECRET_ACCESS_KEY") # noqa: N806 + AWS_REGION_NAME = os.environ.get("AWS_S3_REGION_NAME") # noqa: N806 + AWS_BUCKET_NAME = os.environ.get("AWS_S3_BUCKET_NAME") # noqa: N806 session = Session( aws_access_key_id=AWS_ACCESS_KEY_ID, diff --git a/core/management/commands/handle_emails.py b/core/management/commands/handle_emails.py index 744c3b336..3d33d42c6 100644 --- a/core/management/commands/handle_emails.py +++ b/core/management/commands/handle_emails.py @@ -32,7 +32,7 @@ def send_event_emails( if ignore_approximate_events and event.date_is_approximate: continue - recipients = list(set([event.email] + list(event.team.all().values_list("email", flat=True)))) + recipients = list(set([event.email] + list(event.team.all().values_list("email", flat=True)))) # noqa: RUF005 context = { "event": event, "settings": settings, diff --git a/core/management_utils.py b/core/management_utils.py index 35e0e3ec4..489e8a2a6 100644 --- a/core/management_utils.py +++ b/core/management_utils.py @@ -69,7 +69,7 @@ def brag_on_slack_bang(city, country, team): text = ( f":django_pony: :zap: Woohoo! :tada: New Django Girls alert! " f"Welcome Django Girls {city}, {country}. " - f"Congrats {', '.join(['{} {}'.format(x.first_name, x.last_name) for x in team])}!" + f"Congrats {', '.join([f'{x.first_name} {x.last_name}' for x in team])}!" ) post_message_to_slack("#general", text) diff --git a/core/models/event.py b/core/models/event.py index 0e8b695e9..818a847a2 100644 --- a/core/models/event.py +++ b/core/models/event.py @@ -76,21 +76,25 @@ class Event(models.Model): blank=True, ) - objects = EventManager() - all_objects = models.Manager() # This includes deleted objects - # Flags for email states thank_you_email_sent = models.DateTimeField(null=True, blank=True) submit_information_email_sent = models.DateTimeField(null=True, blank=True) offer_help_email_sent = models.DateTimeField(null=True, blank=True) - def __str__(self): - return f"{self.name}, {self.date}" + all_objects = models.Manager() # This includes deleted objects + objects = EventManager() class Meta: ordering = ("-date",) verbose_name_plural = "List of events" + def __str__(self): + return f"{self.name}, {self.date}" + + def delete(self, using=None, keep_parents=False): + self.is_deleted = True + self.save() + def is_upcoming(self): now = timezone.now() now = ApproximateDate(year=now.year, month=now.month, day=now.day) @@ -158,10 +162,6 @@ def has_organizer(self, user): def has_stats(self): return bool(self.applicants_count and self.attendees_count) - def delete(self, using=None, keep_parents=False): - self.is_deleted = True - self.save() - def add_default_content(self): """Populate EventPageContent with default layout""" data = get_default_eventpage_data() @@ -265,13 +265,13 @@ class EventPageContent(models.Model): coaches = models.ManyToManyField(to="coach.Coach", verbose_name="Coaches") sponsors = models.ManyToManyField(to="sponsor.Sponsor", verbose_name="Sponsors") - def __str__(self): - return f"{self.name} at {self.event}" - class Meta: ordering = ("position",) verbose_name = "Website Content" + def __str__(self): + return f"{self.name} at {self.event}" + class EventPageMenu(models.Model): event = models.ForeignKey(to=Event, null=False, blank=False, related_name="menu", on_delete=models.deletion.CASCADE) @@ -279,9 +279,9 @@ class EventPageMenu(models.Model): url = models.CharField(max_length=255, help_text="http://djangogirls.org/city/") position = models.PositiveIntegerField(help_text="Order of menu") - def __str__(self): - return self.title - class Meta: ordering = ("position",) verbose_name = "Website Menu" + + def __str__(self): + return self.title diff --git a/core/quotes.py b/core/quotes.py index 0da91567d..394bda72c 100644 --- a/core/quotes.py +++ b/core/quotes.py @@ -12,7 +12,7 @@ }, { "text": "Django Girls Lagos brought about the creation of a Nigerian Python community that " - "didn’t exist before. It brought together a lot of great minds who are passionate " + "didn't exist before. It brought together a lot of great minds who are passionate " "about promoting the use of Python in Nigeria.", "name": "Aisha Bello", "url": "http://blog.djangogirls.org/post/138923168468/your-django-story-meet-aisha-bello", @@ -27,7 +27,7 @@ "url": "http://blog.djangogirls.org/post/170349070483/your-django-story-meet-muriel-green", }, { - "text": "Don’t get scared, get started. Almost everything is doable by anyone. Most of the" + "text": "Don't get scared, get started. Almost everything is doable by anyone. Most of the" " things are easy to learn. So start doing what you like. There are lot of things" " on the web to help you out. Just be truthful to yourself.", "name": "Tapasweni Pathak", @@ -48,7 +48,7 @@ "url": "http://blog.djangogirls.org/post/137758290583/your-django-story-meet-p%C3%A4ivi-suomela", }, { - "text": "You don’t have know everything, you just have to be open to learning anything.", + "text": "You don't have know everything, you just have to be open to learning anything.", "name": "Heather Bryant", "url": "http://blog.djangogirls.org/post/137097700508/your-django-story-meet-heather-bryant", }, diff --git a/core/templatetags/core_tags.py b/core/templatetags/core_tags.py index bc725a81a..637536c5b 100644 --- a/core/templatetags/core_tags.py +++ b/core/templatetags/core_tags.py @@ -8,8 +8,6 @@ @register.simple_tag def build_menu_item_url(menu_url, event_page_url): parse_result = urlparse(menu_url) - if parse_result.netloc: # A full URI with domain. - return menu_url - elif parse_result.path.startswith("/"): # Absolute path. + if parse_result.netloc or parse_result.path.startswith("/"): return menu_url return f"/{event_page_url}/{menu_url}" diff --git a/core/views.py b/core/views.py index d1fa55f45..c3b2c05cb 100644 --- a/core/views.py +++ b/core/views.py @@ -72,11 +72,7 @@ def event(request, page_url): user_is_organizer = user.is_authenticated and event_obj.has_organizer(user) is_preview = "preview" in request.GET can_preview = user.is_superuser or user_is_organizer or is_preview - - if event_obj.date: - is_past = event_obj.date <= now_approx - else: - is_past = False + is_past = event_obj.date <= now_approx if event_obj.date else False if not (event_obj.is_page_live or can_preview) or event_obj.is_frozen: return render( @@ -177,8 +173,8 @@ def coc_legacy(request, lang=None): template_name = f"core/coc/{lang}.html" try: return render(request, template_name) - except TemplateDoesNotExist: - raise Http404(_("No translation for language %(lang)s") % {"lang": lang}) + except TemplateDoesNotExist as err: + raise Http404(_("No translation for language %(lang)s") % {"lang": lang}) from err # This view's URL is commented out, so avoid coverage hit by commenting out the view also diff --git a/djangogirls/settings.py b/djangogirls/settings.py index 8405efe64..39264ea67 100644 --- a/djangogirls/settings.py +++ b/djangogirls/settings.py @@ -19,10 +19,7 @@ def gettext(s): DEBUG = os.getenv("DJANGO_DEBUG") != "FALSE" -if DEBUG: - SECRET_KEY = "hello!" -else: - SECRET_KEY = os.getenv("DJANGO_SECRET_KEY") +SECRET_KEY = "hello!" if DEBUG else os.getenv("DJANGO_SECRET_KEY") SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") ALLOWED_HOSTS = ["*"] diff --git a/organize/forms.py b/organize/forms.py index 35190bf4c..ced72c22c 100644 --- a/organize/forms.py +++ b/organize/forms.py @@ -16,7 +16,7 @@ PREVIOUS_ORGANIZER_CHOICES = ( (True, _("Yes, I organized Django Girls")), - (False, _("No, it’s my first time organizing Django Girls")), + (False, _("No, it's my first time organizing Django Girls")), ) WORKSHOP_CHOICES = ((True, _("Remote")), (False, _("In-Person"))) @@ -93,7 +93,7 @@ class OrganizerForm(forms.Form): class WorkshopForm(forms.Form): date = ApproximateDateFormField(widget=forms.TextInput(attrs={"class": "compact-input"})) city = forms.CharField(required=True, max_length=200, widget=forms.TextInput(attrs={"class": "compact-input"})) - country = LazyTypedChoiceField(choices=[(None, "Choose country")] + list(countries)) + country = LazyTypedChoiceField(choices=[(None, _("Choose country")), *list(countries)]) venue = forms.CharField(widget=forms.Textarea(attrs={"class": "compact-input"})) sponsorship = forms.CharField(widget=forms.Textarea(attrs={"class": "compact-input"})) coaches = forms.CharField(widget=forms.Textarea(attrs={"class": "compact-input"})) @@ -125,7 +125,7 @@ def clean_local_restrictions(self): class RemoteWorkshopForm(forms.Form): date = ApproximateDateFormField(widget=forms.TextInput(attrs={"class": "compact-input"})) city = forms.CharField(required=True, max_length=200, widget=forms.TextInput(attrs={"class": "compact-input"})) - country = LazyTypedChoiceField(choices=[(None, _("Choose country"))] + list(countries)) + country = LazyTypedChoiceField(choices=[(None, _("Choose country")), *list(countries)]) sponsorship = forms.CharField(widget=forms.Textarea(attrs={"class": "compact-input"})) coaches = forms.CharField(widget=forms.Textarea(attrs={"class": "compact-input"})) tools = forms.CharField(widget=forms.Textarea(attrs={"class": "compact-input"})) diff --git a/organize/models.py b/organize/models.py index 60cb6fe68..80f7ff670 100644 --- a/organize/models.py +++ b/organize/models.py @@ -61,7 +61,7 @@ def create(self, **data_dict): ) } ) - except ValueError: + except ValueError as err: if date(event_date.year, event_date.month, event_date.day) - date( previous_event.date.year, previous_event.date.month, 1 ) < timedelta(days=180): @@ -71,7 +71,7 @@ def create(self, **data_dict): "Your workshops should be at least 6 months apart. " "Please read our Organizer Manual." ) } - ) + ) from err return super().create(**data_dict) @@ -205,10 +205,7 @@ def deploy(self): Event.objects.filter(city=self.city, country=self.get_country_display()).order_by("-date").first() ) - if previous_event: - event = copy_event(previous_event, self.date) - else: - event = self.create_event() + event = copy_event(previous_event, self.date) if previous_event else self.create_event() # add main organizer of the Event main_organizer = event.add_organizer( @@ -264,7 +261,7 @@ def reject(self): - changes status to REJECTED - sends a rejection email """ - if not self.status == REJECTED: + if self.status != REJECTED: self.change_status_to(REJECTED) send_application_rejection_email(event_application=self) @@ -280,3 +277,6 @@ class Coorganizer(models.Model): class Meta: verbose_name = _("Co-organizer") verbose_name_plural = _("Co-organizers") + + def __str__(self): + return f"{self.first_name} {self.last_name} <{self.email}>" diff --git a/patreonmanager/admin.py b/patreonmanager/admin.py index c7a4b7136..e18b2776f 100644 --- a/patreonmanager/admin.py +++ b/patreonmanager/admin.py @@ -1,8 +1,7 @@ from django.contrib import admin, messages from django.urls import reverse from django.utils.html import format_html -from django.utils.translation import gettext_lazy as _ -from django.utils.translation import ngettext +from django.utils.translation import gettext_lazy as _, ngettext from .filters import PendingRewardsFilter from .models import FundraisingStatus, Patron, Payment, Reward diff --git a/patreonmanager/filters.py b/patreonmanager/filters.py index 08fb0b0a0..9417df74b 100644 --- a/patreonmanager/filters.py +++ b/patreonmanager/filters.py @@ -36,7 +36,7 @@ def queryset(self, request, queryset): reward__name="Special Support Reward", ) c = Counter(payment.patron for payment in payments.select_related("patron")) - for patron, count in c.most_common(): + for patron, _ in c.most_common(): patron_pks.append(patron.pk) return queryset.filter(pk__in=set(patron_pks)) diff --git a/patreonmanager/models.py b/patreonmanager/models.py index 5cad1b8ea..a0d0efece 100644 --- a/patreonmanager/models.py +++ b/patreonmanager/models.py @@ -66,6 +66,9 @@ class Meta: verbose_name_plural = _("payments") unique_together = (("patron", "month"),) + def __str__(self): + return f"{self.patron}, {self.month}" + def get_month_display(self): return self.month.strftime("%B %Y") @@ -80,6 +83,9 @@ class FundraisingStatus(models.Model): class Meta: ordering = ("-date_updated",) + def __str__(self): + return f"{self.id} updated {self.date_updated}, raised {self.amount_raised}" + @property def percentage_of_goal(self): return int(float(self.amount_raised) / float(self.GOAL) * 100.0) diff --git a/patreonmanager/utils/download.py b/patreonmanager/utils/download.py index e8951c521..308f2c6e0 100644 --- a/patreonmanager/utils/download.py +++ b/patreonmanager/utils/download.py @@ -88,7 +88,7 @@ def _get_datetime_from_title(title): match = NEW_MONTH_TITLE_RE.search(title.strip()) try: assert match is not None - except: + except: # noqa: E722 match = MONTH_TITLE_RE.search(title.strip()) assert match is not None diff --git a/pyproject.toml b/pyproject.toml index ce0c54305..24fbb6bcd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,21 +2,88 @@ line-length = 120 target-version = ["py310"] -[tool.isort] -multi_line_output = 3 -include_trailing_comma = true -force_grid_wrap = 0 -use_parentheses = true -ensure_newline_before_comments = true -line_length = 120 -skip = [ - ".git", - ".venv", - ".github", - "mediafiles", - "static", - "templates", - "locale", - "gulp", - "node_modules", +[tool.ruff] +# https://beta.ruff.rs/docs/configuration/ +line-length = 120 +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "FBT", # flake8-boolean-trap + "B", # flake8-bugbear + "C", # flake8-comprehensions + "DJ", # flake8-django + "INT", # flake8-gettext + "PIE", # flake8-pie + "Q", # flake8-quotes + "SIM", # flake8-simplify + "PGH", # pygrep-hooks + "PLE", # pylint error + "PLR", # pylint refactor + "PLW", # pylint warning + "UP", # pyupgrade + "C901", # mccabe + "N", # pep8-naming + "YTT", # flake8-2020, + "RUF" +] + +exclude = [ + ".eggs", + ".git", + ".mypy_cache", + ".ruff_cache", + ".env", + ".venv", + "**migrations/**", + "node_modules", + "venv", +] + +ignore = [ + "B006", # Do not use mutable data structures for argument defaults + "B011", # tests use assert False + "B019", # Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks + "B905", # `zip()` without an explicit `strict=` parameter + "C901", # too complex functions + "DJ001", # Avoid using `null=True` on string-based fields such as CharField + "E402", # module level import not at top of file + "E731", # do not assign a lambda expression, use a def + "FBT002", # Boolean default value in function definition + "FBT003", # Boolean positional value in function call + "PLR0911", # Too many return statements + "PLR0912", # Too many branches + "PLR0913", # Too many arguments to function call + "PLR0915", # Too many statements + "PLR2004", # Magic value used in comparison, consider replacing with a constant variable + "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` + "SIM102", # Use a single `if` statement instead of nested `if` statements +] + +[tool.ruff.per-file-ignores] +"__init__.py" = [ + "F401" # unused-import +] +"djangogirls/urls.py" = [ + "RUF005" # Consider iterable unpacking instead of concatenation +] + +[tool.ruff.isort] +combine-as-imports = true +known-first-party = [ + "applications", + "coach", + "contact", + "core", + "djangogirls", + "donations", + "globalpartners", + "jobboard", + "organize", + "patreonmanager", + "pictures", + "sponsor", + "story", ] +extra-standard-library = ["dataclasses"] diff --git a/sponsor/models.py b/sponsor/models.py index 424de6c0a..d360e8f44 100644 --- a/sponsor/models.py +++ b/sponsor/models.py @@ -13,12 +13,12 @@ class Sponsor(models.Model): url = models.URLField(null=True, blank=True) description = models.TextField(null=True, blank=True) - def __str__(self): - return self.name - class Meta: ordering = ("name",) + def __str__(self): + return self.name + def logo_display_for_admin(self): if self.logo: return f'' @@ -33,8 +33,8 @@ class Donor(models.Model): amount = models.FloatField() visible = models.BooleanField(default=False) - def __str__(self): - return self.name - class Meta: ordering = ("amount",) + + def __str__(self): + return self.name diff --git a/story/management/commands/fetch_stories.py b/story/management/commands/fetch_stories.py index 0a58b030f..fc27a03ac 100644 --- a/story/management/commands/fetch_stories.py +++ b/story/management/commands/fetch_stories.py @@ -4,7 +4,7 @@ from django.core.files import File from django.core.files.temp import NamedTemporaryFile from django.core.management.base import BaseCommand -from pyquery import PyQuery as pq +from pyquery import PyQuery as pq # noqa: N813 from story.models import Story @@ -34,9 +34,9 @@ def handle(self, *args, **options): if not Story.objects.filter(name=name).exists(): post_url = post.find("link").text - post = pq(post.find("description").text) - image_url = post("img").attr.src - story = Story(name=name, post_url=post_url, content=post, is_story=is_story) + _post = pq(post.find("description").text) + image_url = _post("img").attr.src + story = Story(name=name, post_url=post_url, content=_post, is_story=is_story) if image_url: img = NamedTemporaryFile(delete=True) diff --git a/story/management/commands/fetch_tumblr_stories.py b/story/management/commands/fetch_tumblr_stories.py index ea213dfd1..1d56d7711 100644 --- a/story/management/commands/fetch_tumblr_stories.py +++ b/story/management/commands/fetch_tumblr_stories.py @@ -12,7 +12,8 @@ def download_image(url: str) -> tuple[str, File]: image_name = urlparse(url).path.split("/")[-1] content = urlretrieve(url) - return image_name, File(open(content[0], "rb")) + # TODO: could this be done differently without opening the file here? + return image_name, File(open(content[0], "rb")) # noqa: SIM115 class Command(BaseCommand): diff --git a/story/models.py b/story/models.py index f89df7838..b755076e8 100644 --- a/story/models.py +++ b/story/models.py @@ -11,12 +11,12 @@ class Story(models.Model): # False means a regular blogpost, not a story is_story = models.BooleanField(default=True) + class Meta: + verbose_name = _("story") + verbose_name_plural = _("stories") + def __str__(self): return self.name def get_absolute_url(self): return self.post_url - - class Meta: - verbose_name = _("story") - verbose_name_plural = _("stories") diff --git a/tests/applications/models/test_applications.py b/tests/applications/models/test_applications.py index be639652e..92a86ba55 100644 --- a/tests/applications/models/test_applications.py +++ b/tests/applications/models/test_applications.py @@ -24,8 +24,8 @@ def test_number_of_applications(form): def test_average_score(application, user, another_user): assert application.average_score != 4 - score_1 = Score.objects.create(user=user, application=application, score=3) - score_2 = Score.objects.create(user=another_user, application=application, score=5) + Score.objects.create(user=user, application=application, score=3) + Score.objects.create(user=another_user, application=application, score=5) assert application.average_score == 4 @@ -68,7 +68,7 @@ def test_is_accepted(application): application.state = state[0] application.save() - is_accepted = True if state[0] == "accepted" else False + is_accepted = state[0] == "accepted" assert application.is_accepted == is_accepted diff --git a/tests/applications/views/test_applications_download.py b/tests/applications/views/test_applications_download.py index 4d0ba661f..ce4223ab8 100644 --- a/tests/applications/views/test_applications_download.py +++ b/tests/applications/views/test_applications_download.py @@ -49,12 +49,8 @@ def test_download_applications_list_with_question_added( # now create a new application with answer to the new question new_application = Application.objects.create(form=future_event_form, state="submitted") - new_application_questionx_answer = Answer.objects.create( # NOQA - application=new_application, question=questionx, answer="answer to questionx for app 5" - ) - new_application_5_last_answer = Answer.objects.create( # NOQA - application=new_application, question=last_question, answer="answer to last for app 5" - ) + Answer.objects.create(application=new_application, question=questionx, answer="answer to questionx for app 5") + Answer.objects.create(application=new_application, question=last_question, answer="answer to last for app 5") resp = admin_client.get(applications_url) diff --git a/tests/applications/views/test_apply.py b/tests/applications/views/test_apply.py index df9fba1fb..41a6af5e5 100644 --- a/tests/applications/views/test_apply.py +++ b/tests/applications/views/test_apply.py @@ -79,23 +79,23 @@ def test_event_not_live(client, hidden_event): def test_application_form_valid(client, future_event_form, future_event): - question_data = { - "question_1": "Bill Smith", - "question_2": "bill.smith@djangogirls.org", - "question_3": "0123445689", - "question_4": "London, United Kingdom", - "question_5": "", # Not required - "question_6": "Mac OS X", - "question_7": "I work as a programmer", - "question_8": "", - "question_9": "Developer", - "question_10": "To test things", - "question_11": "test", - "question_12": "Facebook", - "question_13": "Yes", - "question_14": "I've read and understood the Django Girls Code of Conduct", - "newsletter_optin": True, - } + # question_data = { + # "question_1": "Bill Smith", + # "question_2": "bill.smith@djangogirls.org", + # "question_3": "0123445689", + # "question_4": "London, United Kingdom", + # "question_5": "", # Not required + # "question_6": "Mac OS X", + # "question_7": "I work as a programmer", + # "question_8": "", + # "question_9": "Developer", + # "question_10": "To test things", + # "question_11": "test", + # "question_12": "Facebook", + # "question_13": "Yes", + # "question_14": "I've read and understood the Django Girls Code of Conduct", + # "newsletter_optin": True, + # } # TODO: Need to find a way to get the question numbers as they aren't always 1-14 diff --git a/tests/conftest.py b/tests/conftest.py index bd17725d2..6622cb116 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,7 +8,7 @@ from globalpartners.models import GlobalPartner from pictures.models import StockPicture from sponsor.models import Donor -from tests.mocks import * # noqa +from tests.mocks import * # noqa: F403 @pytest.fixture(autouse=True) @@ -28,7 +28,7 @@ def user(db, django_user_model, django_username_field): """This is a copy from pytest-django prepared for usage with e-mail instead of username. """ - UserModel = django_user_model + UserModel = django_user_model # noqa: N806 username_field = django_username_field try: @@ -44,7 +44,7 @@ def admin_user(db, django_user_model, django_username_field): """This is a copy from pytest-django prepared for usage with e-mail instead of username. """ - UserModel = django_user_model + UserModel = django_user_model # noqa: N806 username_field = django_username_field try: diff --git a/tests/contact/test_views.py b/tests/contact/test_views.py index 5f6365304..154e89e3f 100644 --- a/tests/contact/test_views.py +++ b/tests/contact/test_views.py @@ -18,7 +18,7 @@ def test_form_sends_email_to_support(client, mailoutbox): "contact_type": ContactEmail.SUPPORT, "g-recaptcha-response": "PASSED", } - resp = client.post(reverse(CONTACT_URL), data=post_data) + client.post(reverse(CONTACT_URL), data=post_data) assert len(mailoutbox) == 1 email = mailoutbox[0] diff --git a/tests/core/test_admin.py b/tests/core/test_admin.py index e9987c8b5..889605fe4 100644 --- a/tests/core/test_admin.py +++ b/tests/core/test_admin.py @@ -18,7 +18,7 @@ def test_get_queryset_for_organizer(client, organizer_peter, future_event, past_ # flattens the list of lists # FIXME results = "".join(sum(resp.context["results"], [])) - assert all([x.name in results for x in [future_event, past_event]]) + assert all(x.name in results for x in [future_event, past_event]) def test_manage_organizers_view_for_superuser(admin_client, events): @@ -27,7 +27,7 @@ def test_manage_organizers_view_for_superuser(admin_client, events): # Only upcoming events are listed expected_events = Event.objects.filter(date__gte=datetime.now().strftime("%Y-%m-%d")).order_by("name") assert len(resp.context["all_events"]) == expected_events.count() - assert all([x.is_upcoming() for x in resp.context["all_events"]]) + assert all(x.is_upcoming() for x in resp.context["all_events"]) # First event is selected automatically assert resp.context["event"] == expected_events[0] @@ -38,12 +38,11 @@ def test_manage_organizers_view_for_organizers(client, organizer_peter, events): client.force_login(organizer_peter) resp = client.get(reverse("admin:core_event_manage_organizers")) assert len(resp.context["all_events"]) == expected_events.count() - assert all([x.is_upcoming() for x in resp.context["all_events"]]) + assert all(x.is_upcoming() for x in resp.context["all_events"]) @mock.patch("core.models.User.invite_to_slack") def test_adding_organizer_as_superuser(invite_to_slack, admin_client, future_event, hidden_event, django_user_model): - add_organizers_url = reverse("admin:core_event_add_organizers") total_count = django_user_model.objects.filter(is_staff=True).count() team_count = future_event.team.count() data = {"event": future_event.pk, "name": "New organizer", "email": "new-superuser@organizer.com"} diff --git a/tests/core/test_gmail_accounts.py b/tests/core/test_gmail_accounts.py index 13bd1cd3e..5768cbea1 100644 --- a/tests/core/test_gmail_accounts.py +++ b/tests/core/test_gmail_accounts.py @@ -54,4 +54,4 @@ def test_migrate_gmail_account(second_veryrandom_event, veryrandom_event): veryrandom_event.refresh_from_db() assert old_email == veryrandom_event.email - assert "veryrandom12017@djangogirls.org" == second_veryrandom_event.email + assert second_veryrandom_event.email == "veryrandom12017@djangogirls.org" diff --git a/tests/core/test_utils.py b/tests/core/test_utils.py index 1036e60fa..4a535126c 100644 --- a/tests/core/test_utils.py +++ b/tests/core/test_utils.py @@ -75,14 +75,14 @@ def test_day_before_deadline(): assert result == date(2016, 10, 16) -@freeze_time("2016-10-16") # noqa: F811 +@freeze_time("2016-10-16") def test_day_before_deadline(): # noqa: F811 result = next_deadline() assert result == date(2016, 10, 16) -@freeze_time("2016-10-17") # noqa: F811 +@freeze_time("2016-10-17") def test_day_before_deadline(): # noqa: F811 result = next_deadline() diff --git a/tests/core/test_views.py b/tests/core/test_views.py index b6eb84d87..f4e51aeb5 100644 --- a/tests/core/test_views.py +++ b/tests/core/test_views.py @@ -179,14 +179,14 @@ def test_event_multiple_events_same_page_url(client, future_event, old_event): def test_coc_legacy(client): - AVAILABLE_LANG = { + available_lang = { "en": "

Code of Conduct

", "es": "

Código de Conducta

", "fr": "

Code de Conduite

", "ko": "

준수 사항

", "pt-br": "

Código de Conduta

", } - for lang, title in AVAILABLE_LANG.items(): + for lang, title in available_lang.items(): response = client.get(f"/coc/{lang}/") assert title in response.content.decode("utf-8"), title From f6bacc22ba15132f156be7f4865308f04b9fbe30 Mon Sep 17 00:00:00 2001 From: Mark Walker Date: Fri, 24 Nov 2023 13:13:54 +0000 Subject: [PATCH 2/6] fix: python version to match project --- .github/workflows/ruff.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index 5eb779739..542710e87 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -21,7 +21,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.11" + python-version: "3.10" cache: 'pip' - run: | python -m pip install --upgrade pip From 763b8fdaa2534b434059de1eb84d11c6d51473b6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 24 Nov 2023 13:16:23 +0000 Subject: [PATCH 3/6] ci: auto fixes from pre-commit hooks for more information, see https://pre-commit.ci --- djangogirls/utils/sanitize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangogirls/utils/sanitize.py b/djangogirls/utils/sanitize.py index fc64f3ceb..109629de7 100644 --- a/djangogirls/utils/sanitize.py +++ b/djangogirls/utils/sanitize.py @@ -74,7 +74,7 @@ def sanitize_bool(value, strict=False): elif isinstance(value, int): return value > 0 - if isinstance(value, (list, tuple)) and len(value) == 1: + if isinstance(value, list | tuple) and len(value) == 1: # recurse return sanitize_bool(value[0], strict=strict) From 2cadbbb883186af183ff15c37d049c1f50242eb5 Mon Sep 17 00:00:00 2001 From: Mark Walker Date: Fri, 24 Nov 2023 13:18:29 +0000 Subject: [PATCH 4/6] fix: remove copied in job --- .github/workflows/ruff.yml | 39 -------------------------------------- 1 file changed, 39 deletions(-) diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index 542710e87..d7476671d 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -28,42 +28,3 @@ jobs: pip install ruff - name: Run Ruff run: ruff . - - npm-production-test: - name: Frontend build test - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Cache node modules - id: cache-npm - uses: actions/cache@v3 - env: - cache-name: cache-node-modules - with: - # npm cache files are stored in `~/.npm` on Linux/macOS - path: ~/.npm - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- - - name: Use Node.js 18.5.0 - uses: actions/setup-node@v4 - with: - node-version: 18.5.0 - - name: npm install - env: - NODE_ENV: production - run: npm ci - working-directory: ./static - - name: npm gulp - env: - NODE_ENV: production - run: npm run gulp - working-directory: ./static - - name: npm build - env: - NODE_ENV: production - run: npm run build - working-directory: ./static From 5a9c1a0968894ee91b898f0054cce57e04689655 Mon Sep 17 00:00:00 2001 From: Mark Walker Date: Sat, 25 Nov 2023 01:36:19 +0000 Subject: [PATCH 5/6] test: bumping test coverage --- .github/workflows/django.yml | 1 - tests/applications/models/test_applications.py | 6 ++++++ tests/applications/views/test_applications.py | 6 +++++- tests/applications/views/test_applications_download.py | 10 ++++++++++ tests/core/test_admin.py | 5 +++++ 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml index 084c16bff..0a9ad82ed 100644 --- a/.github/workflows/django.yml +++ b/.github/workflows/django.yml @@ -60,4 +60,3 @@ jobs: py.test --cov env: POSTGRES_PASSWORD: postgres - diff --git a/tests/applications/models/test_applications.py b/tests/applications/models/test_applications.py index 92a86ba55..7fb0ecc03 100644 --- a/tests/applications/models/test_applications.py +++ b/tests/applications/models/test_applications.py @@ -80,3 +80,9 @@ def test_is_scored_by_user(application, user, another_user): Score.objects.create(user=another_user, application=application, score=0) assert application.is_scored_by_user(another_user) is False + + +def test_score_str(application, user, another_user): + score = Score.objects.create(user=user, application=application, score=3) + + assert str(score) == f"{score.user} - {score.application}. Score {score.score}" diff --git a/tests/applications/views/test_applications.py b/tests/applications/views/test_applications.py index 0acaa7e81..c5ec78665 100644 --- a/tests/applications/views/test_applications.py +++ b/tests/applications/views/test_applications.py @@ -13,7 +13,7 @@ def test_access_applications_view(client, user_client, admin_client, future_even resp = client.get(applications_url) assert resp.status_code == 302 - # as logged in user, but not orgarniser of given event + # as logged in user, but not organiser of given event resp = user_client.get(applications_url) assert resp.status_code == 404 @@ -125,6 +125,10 @@ def get_filtered_applications_list( assert resp.context["applications"] == [application_waitlisted] +def test_application_str(admin_client, future_event, application_submitted): + assert str(application_submitted.id) == str(application_submitted) + + def test_changing_application_status(admin_client, future_event, application_submitted): assert application_submitted.state == "submitted" resp = admin_client.post( diff --git a/tests/applications/views/test_applications_download.py b/tests/applications/views/test_applications_download.py index ce4223ab8..aace8be84 100644 --- a/tests/applications/views/test_applications_download.py +++ b/tests/applications/views/test_applications_download.py @@ -74,3 +74,13 @@ def test_download_applications_list_with_question_added( # column assert csv_list[5][17] == "answer to last for app 5" assert csv_list[5][18] == "answer to questionx for app 5" + + +def test_answer_str(admin_client, application_submitted, future_event, future_event_form, applications): + last_question = future_event_form.question_set.last() + + new_application = Application.objects.create(form=future_event_form, state="submitted") + answer = Answer.objects.create( + application=new_application, question=last_question, answer="answer to last for app 5" + ) + assert str(answer) == f"{answer.application} - {answer.question}" diff --git a/tests/core/test_admin.py b/tests/core/test_admin.py index 889605fe4..f535714d2 100644 --- a/tests/core/test_admin.py +++ b/tests/core/test_admin.py @@ -163,3 +163,8 @@ def test_unfreeze_events_action(admin_client, future_event): assert response.status_code == 200 assert Event.objects.filter(is_frozen=False).count() == 1 assert Event.objects.filter(is_on_homepage=True).count() == 1 + + +def test_event_str(admin_client, events): + event = events[0] + assert str(event) == f"{event.name}, {event.date}" From 44a4b344be2d969ecdd592de2a8e71164a1677d2 Mon Sep 17 00:00:00 2001 From: Mark Walker Date: Sat, 25 Nov 2023 21:52:39 +0000 Subject: [PATCH 6/6] test: Add model str tests --- tests/core/test_deploy_event.py | 21 +++++++++++++++++++-- tests/organize/test_models.py | 9 ++++++++- tests/patreon/test_models.py | 11 +++++++++++ tests/sponsor/__init__.py | 0 tests/sponsor/conftest.py | 14 ++++++++++++++ tests/sponsor/test_models.py | 6 ++++++ 6 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 tests/patreon/test_models.py create mode 100644 tests/sponsor/__init__.py create mode 100644 tests/sponsor/conftest.py create mode 100644 tests/sponsor/test_models.py diff --git a/tests/core/test_deploy_event.py b/tests/core/test_deploy_event.py index a396b15b5..f92cd2a67 100644 --- a/tests/core/test_deploy_event.py +++ b/tests/core/test_deploy_event.py @@ -2,7 +2,7 @@ from coach.models import Coach from core.deploy_event import copy_content_from_previous_event, copy_event, copy_menu_from_previous_event -from core.models import Event +from core.models import Event, EventPageContent, EventPageMenu from sponsor.models import Sponsor @@ -38,7 +38,7 @@ def test_copy_event(past_event): new_event = copy_event(past_event, new_date) - # we need to refetch the event as we changed id of the object + # we need to re-fetch the event as we changed id of the object # inside copy_event method past_event = Event.objects.get(pk=previous_event_id) @@ -50,3 +50,20 @@ def test_copy_event(past_event): assert past_event.country == new_event.country assert past_event.latlng == new_event.latlng assert past_event.main_organizer == new_event.main_organizer + + +def test_eventpagecontent_str(past_event): + event_content = EventPageContent.objects.create( + name="coach", + content="

Be a Mentor!

", + background="event/backgrounds/photo0_cBUZ8zp.jpg", + is_public=True, + position=40, + event=past_event, + ) + assert str(event_content) == f"{event_content.name} at {event_content.event}" + + +def test_eventpagemenu_str(past_event): + menu = EventPageMenu.objects.create(url="#values", position=1, event=past_event, title="About") + assert str(menu) == f"{menu.title}" diff --git a/tests/organize/test_models.py b/tests/organize/test_models.py index d3c17e9fa..df13c1502 100644 --- a/tests/organize/test_models.py +++ b/tests/organize/test_models.py @@ -6,7 +6,7 @@ from core.models import Event from organize.constants import DEPLOYED, ON_HOLD, REJECTED -from organize.models import EventApplication +from organize.models import Coorganizer, EventApplication def test_comment_required_for_on_hold_application(base_application): @@ -119,3 +119,10 @@ def test_previous_application_with_approximate_date(data_dict, previous_applicat assert EventApplication.object.count() == 1 EventApplication.object.create(**data_dict) assert EventApplication.object.count() == 2 + + +def test_coorganizer_str(base_application): + org = Coorganizer.objects.create( + event_application=base_application, email="anna@example.com", first_name="Anna", last_name="Smith" + ) + assert str(org) == f"{org.first_name} {org.last_name} <{org.email}>" diff --git a/tests/patreon/test_models.py b/tests/patreon/test_models.py new file mode 100644 index 000000000..5ffbd2d39 --- /dev/null +++ b/tests/patreon/test_models.py @@ -0,0 +1,11 @@ +from patreonmanager.models import FundraisingStatus, Payment + + +def test_payment_str(payment): + payment = Payment.objects.get() + assert str(payment) == f"{payment.patron}, {payment.month}" + + +def test_fundraisingstatus_str(): + status = FundraisingStatus.objects.create(number_of_patrons=1, amount_raised=1) + assert str(status) == f"{status.id} updated {status.date_updated}, raised {status.amount_raised}" diff --git a/tests/sponsor/__init__.py b/tests/sponsor/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/sponsor/conftest.py b/tests/sponsor/conftest.py new file mode 100644 index 000000000..6e9f912fb --- /dev/null +++ b/tests/sponsor/conftest.py @@ -0,0 +1,14 @@ +import pytest + +from sponsor.models import Donor, Sponsor + + +@pytest.fixture() +def donor(): + donor = Donor.objects.create(name="Ola", amount=50, visible=True) + return donor + + +@pytest.fixture() +def sponsor(): + return Sponsor.objects.create(name="Company name") diff --git a/tests/sponsor/test_models.py b/tests/sponsor/test_models.py new file mode 100644 index 000000000..659a12c68 --- /dev/null +++ b/tests/sponsor/test_models.py @@ -0,0 +1,6 @@ +def test_donor(donor): + assert str(donor) == donor.name + + +def test_sponsor(sponsor): + assert str(sponsor) == sponsor.name