Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit a637fec

Browse files
committedJan 8, 2025··
Merge branch 'main' into rel
2 parents 5ca35e2 + 36d287c commit a637fec

18 files changed

+311
-73
lines changed
 

‎CHANGELOG.rst

+17
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,23 @@ CHANGELOG
66
.. This is included by docs/developer/changelog.rst
77
88
9+
Version v5.13.0
10+
---------------
11+
12+
This release has some minor bug fixes.
13+
The main difference is to add a per-domain report for advertisers
14+
to show more visibility on where their ads are appearing.
15+
We are also switching to UUID7s from UUID4s for the few places we use UUIDs.
16+
17+
:Date: January 8, 2025
18+
19+
* @davidfischer: Fix typo in publisher-placement report (#973)
20+
* @davidfischer: Switch to UUID7s (#971)
21+
* @davidfischer: Advertiser domain report (#968)
22+
* @davidfischer: Only report on domains with 1 view (#967)
23+
* @JasonBarnabe: Geo report should include revenue per country (#962)
24+
25+
926
Version v5.12.0
1027
---------------
1128

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Generated by Django 5.0.10 on 2025-01-02 21:27
2+
3+
import uuid_utils.compat
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('adserver', '0101_domainimpression_aggregation'),
11+
]
12+
13+
operations = [
14+
migrations.AlterField(
15+
model_name='historicalpublisherpayout',
16+
name='id',
17+
field=models.UUIDField(db_index=True, default=uuid_utils.compat.uuid7, editable=False),
18+
),
19+
migrations.AlterField(
20+
model_name='offer',
21+
name='id',
22+
field=models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False),
23+
),
24+
migrations.AlterField(
25+
model_name='publisherpayout',
26+
name='id',
27+
field=models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False),
28+
),
29+
]

‎adserver/models.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
import logging
66
import math
77
import re
8-
import uuid
98
from collections import Counter
109

1110
import bleach
1211
import djstripe.models as djstripe_models
1312
import pytz
13+
import uuid_utils.compat as uuid
1414
from django.conf import settings
1515
from django.core.cache import cache
1616
from django.core.cache import caches
@@ -2689,7 +2689,7 @@ class Offer(AdBase):
26892689
MAX_VIEW_TIME = 5 * 60 # seconds
26902690

26912691
# Use an ok user-facing pk value
2692-
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
2692+
id = models.UUIDField(primary_key=True, default=uuid.uuid7, editable=False)
26932693

26942694
advertisement = models.ForeignKey(
26952695
Advertisement,
@@ -2777,7 +2777,7 @@ class Meta:
27772777
class PublisherPayout(TimeStampedModel):
27782778
"""Details on historical publisher payouts."""
27792779

2780-
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
2780+
id = models.UUIDField(primary_key=True, default=uuid.uuid7, editable=False)
27812781
publisher = models.ForeignKey(
27822782
Publisher, related_name="payouts", on_delete=models.PROTECT
27832783
)

‎adserver/reports.py

+16
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from .constants import PAID_CAMPAIGN
99
from .models import AdImpression
1010
from .models import AdvertiserImpression
11+
from .models import DomainImpression
1112
from .models import GeoImpression
1213
from .models import KeywordImpression
1314
from .models import PlacementImpression
@@ -86,6 +87,9 @@ def get_index_display(self, index):
8687
"""Used to add display logic the index field."""
8788
return index
8889

90+
def get_index_header(self):
91+
return "Day (UTC)"
92+
8993
def generate(self):
9094
raise NotImplementedError("Subclasses implement this method")
9195

@@ -188,6 +192,18 @@ class AdvertiserPublisherReport(AdvertiserReport):
188192
select_related_fields = ("advertisement", "advertisement__flight", "publisher")
189193

190194

195+
class AdvertiserDomainReport(AdvertiserReport):
196+
"""Report to breakdown advertiser performance by domain where the ad appears."""
197+
198+
model = DomainImpression
199+
index = "domain"
200+
order = "-views"
201+
select_related_fields = ("advertisement", "advertisement__flight")
202+
203+
def get_index_header(self):
204+
return self.index.title()
205+
206+
191207
class PublisherReport(BaseReport):
192208
"""Report for showing daily ad performance for a publisher."""
193209

‎adserver/tasks.py

+1
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,7 @@ def daily_update_domains(day=None):
535535
total_clicks=Count("domain", filter=Q(clicked=True)),
536536
)
537537
.exclude(domain__isnull=True)
538+
.filter(total_views__gt=0)
538539
.order_by("-total_decisions")
539540
.values(
540541
"advertisement",

‎adserver/templates/adserver/base.html

+10
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,16 @@ <h6 class="text-muted">{{ advertiser }}</h6>
126126
</a>
127127
</li>
128128

129+
<li class="nav-item">
130+
<a
131+
class="nav-link"
132+
href="{% url 'advertiser_domain_report' advertiser.slug %}"
133+
>
134+
<span class="fa fa-laptop fa-fw ml-4 text-muted" aria-hidden="true"></span>
135+
<span>{% trans 'Domains' %}</span>
136+
</a>
137+
</li>
138+
129139
<li class="nav-item">
130140
<a class="nav-link" href="{% url 'advertiser_users' advertiser.slug %}">
131141
<span class="fa fa-users fa-fw mr-2 text-muted" aria-hidden="true"></span>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{% extends "adserver/reports/advertiser.html" %}
2+
{% load humanize %}
3+
{% load i18n %}
4+
5+
6+
{% block title %}{% trans 'Advertiser Domain Report' %} - {{ advertiser }}{% endblock %}
7+
8+
9+
{% block heading %}
10+
{% blocktrans %}Advertiser Domain Report for {{ advertiser }}{% endblocktrans %}
11+
{% endblock heading %}
12+
13+
{% block breadcrumbs %}
14+
{{ block.super }}
15+
<li class="breadcrumb-item active">{% trans 'Advertiser Domain Report' %}</li>
16+
{% endblock breadcrumbs %}
17+
18+
19+
{% block additional_filters %}
20+
{{ block.super }}
21+
22+
<div class="col-xl-3 col-md-6 col-12 mb-3">
23+
<label class="col-form-label" for="id_flight">{% trans 'Flight' %}</label>
24+
<select class="form-control" name="flight" id="id_flight">
25+
<option value="">{% trans 'All flights' %}</option>
26+
{% for flight in flights %}
27+
<option value="{{ flight.slug }}"{% if flight.slug == request.GET.flight %} selected{% endif %}>{{ flight.name }}</option>
28+
{% endfor %}
29+
</select>
30+
</div>
31+
32+
{% endblock additional_filters %}
33+
34+
35+
{% block explainer %}
36+
<section class="mb-5">
37+
<h3>{% trans 'About this report' %}</h3>
38+
<p>{% trans 'This report shows the top domains where your ads are shown.' %}</p>
39+
<em>
40+
{% blocktrans %}This report shows the <strong>top {{ limit }} domains</strong> and updates daily. All previous days data is complete.{% endblocktrans %}
41+
</em>
42+
</section>
43+
{% endblock explainer %}
44+
45+
46+
{% block report %}{% endblock report %}

‎adserver/templates/adserver/reports/includes/advertiser-report-table.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<table class="table table-hover report">
55
<thead>
66
<tr>
7-
<th><strong>{% trans 'Day (UTC)' %}</strong></th>
7+
<th><strong>{{ report.get_index_header }}</strong></th>
88
<th class="text-right"><strong>{% trans 'Views' %}</strong></th>
99
<th class="text-right"><strong>{% trans 'Clicks' %}</strong></th>
1010
<th class="text-right"><strong>{% trans 'Cost' %}</strong></th>

‎adserver/templates/adserver/reports/publisher-placement.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
<h3>About this report</h3>
4141
<p>
4242
This report allows you to break down your different ad placements and view reports on them.
43-
You can use this to optomize the different placement on your site,
43+
You can use this to optimize the different placement on your site,
4444
and better understand how each one is working to be able to improve them.
4545
</p>
4646
<p>

‎adserver/tests/test_reports.py

+52
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,19 @@
2020
from ..models import Advertiser
2121
from ..models import AdvertiserImpression
2222
from ..models import Campaign
23+
from ..models import DomainImpression
2324
from ..models import Flight
2425
from ..models import Offer
2526
from ..models import Publisher
2627
from ..models import PublisherPaidImpression
28+
from ..reports import AdvertiserDomainReport
2729
from ..reports import AdvertiserReport
2830
from ..reports import OptimizedAdvertiserReport
2931
from ..reports import OptimizedPublisherPaidReport
3032
from ..reports import PublisherGeoReport
3133
from ..reports import PublisherReport
3234
from ..tasks import daily_update_advertisers
35+
from ..tasks import daily_update_domains
3336
from ..tasks import daily_update_geos
3437
from ..tasks import daily_update_impressions
3538
from ..tasks import daily_update_keywords
@@ -464,6 +467,55 @@ def test_advertiser_publisher_report_contents(self):
464467
response = self.client.get(export_url)
465468
self.assertContains(response, "Total,3")
466469

470+
def test_advertiser_domain_report_contents(self):
471+
get(
472+
Offer,
473+
advertisement=self.ad1,
474+
publisher=self.publisher1,
475+
viewed=True,
476+
domain="example.com",
477+
)
478+
get(
479+
Offer,
480+
advertisement=self.ad1,
481+
publisher=self.publisher2,
482+
viewed=True,
483+
clicked=True,
484+
domain="example.com",
485+
)
486+
get(
487+
Offer,
488+
advertisement=self.ad1,
489+
publisher=self.publisher2,
490+
viewed=True,
491+
clicked=False,
492+
domain="example2.com",
493+
)
494+
495+
# Update reporting
496+
daily_update_domains()
497+
498+
url = reverse("advertiser_domain_report", args=[self.advertiser1.slug])
499+
500+
# Anonymous
501+
response = self.client.get(url)
502+
self.assertEqual(response.status_code, 302)
503+
self.assertTrue(response["location"].startswith("/accounts/login/"))
504+
505+
self.client.force_login(self.staff_user)
506+
507+
response = self.client.get(url)
508+
self.assertContains(response, "example.com")
509+
self.assertContains(response, "example2.com")
510+
511+
report = AdvertiserDomainReport(DomainImpression.objects.filter(advertisement=self.ad1))
512+
report.generate()
513+
514+
# Check the actual data
515+
self.assertEqual(len(report.results), 2)
516+
self.assertAlmostEqual(report.total["views"], 3)
517+
self.assertAlmostEqual(report.total["clicks"], 1)
518+
467519
def test_advertiser_keyword_report(self):
468520
url = reverse("advertiser_keyword_report", args=[self.advertiser1.slug])
469521

‎adserver/urls.py

+6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from .views import AdvertiserAuthorizedUsersInviteView
1616
from .views import AdvertiserAuthorizedUsersRemoveView
1717
from .views import AdvertiserAuthorizedUsersView
18+
from .views import AdvertiserDomainReportView
1819
from .views import AdvertiserFlightReportView
1920
from .views import AdvertiserGeoReportView
2021
from .views import AdvertiserKeywordReportView
@@ -194,6 +195,11 @@
194195
AdvertiserTopicReportView.as_view(),
195196
name="advertiser_topic_report",
196197
),
198+
path(
199+
r"advertiser/<slug:advertiser_slug>/report/domains/",
200+
AdvertiserDomainReportView.as_view(),
201+
name="advertiser_domain_report",
202+
),
197203
path(
198204
r"advertiser/<slug:advertiser_slug>/flights/",
199205
FlightListView.as_view(),

‎adserver/views.py

+57
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
from .models import Advertiser
9494
from .models import AdvertiserImpression
9595
from .models import Campaign
96+
from .models import DomainImpression
9697
from .models import Flight
9798
from .models import GeoImpression
9899
from .models import KeywordImpression
@@ -107,6 +108,7 @@
107108
from .models import RegionTopicImpression
108109
from .models import Topic
109110
from .models import UpliftImpression
111+
from .reports import AdvertiserDomainReport
110112
from .reports import AdvertiserPublisherReport
111113
from .reports import AdvertiserReport
112114
from .reports import OptimizedAdvertiserReport
@@ -1639,6 +1641,61 @@ def get_context_data(self, **kwargs):
16391641
return context
16401642

16411643

1644+
class AdvertiserDomainReportView(AdvertiserAccessMixin, BaseReportView):
1645+
LIMIT = 50
1646+
DATA_COLLECTION_START_DATE = datetime(
1647+
year=2024, month=12, day=1, tzinfo=timezone.get_current_timezone()
1648+
)
1649+
1650+
impression_model = DomainImpression
1651+
template_name = "adserver/reports/advertiser-domain.html"
1652+
1653+
def get_context_data(self, **kwargs):
1654+
context = super().get_context_data(**kwargs)
1655+
1656+
advertiser_slug = kwargs.get("advertiser_slug", "")
1657+
advertiser = get_object_or_404(Advertiser, slug=advertiser_slug)
1658+
1659+
flight_slug = self.request.GET.get("flight", "")
1660+
flight = Flight.objects.filter(
1661+
campaign__advertiser=advertiser, slug=flight_slug
1662+
).first()
1663+
1664+
if context["start_date"] < self.DATA_COLLECTION_START_DATE:
1665+
messages.info(
1666+
self.request,
1667+
_(
1668+
"Data for the domain report started being collected in %s. Data for this date range may be incomplete."
1669+
)
1670+
% (self.DATA_COLLECTION_START_DATE.strftime("%B %Y")),
1671+
)
1672+
1673+
queryset = self.get_queryset(
1674+
advertiser=advertiser,
1675+
flight=flight,
1676+
start_date=context["start_date"],
1677+
end_date=context["end_date"],
1678+
)
1679+
1680+
report = AdvertiserDomainReport(
1681+
queryset,
1682+
max_results=self.LIMIT,
1683+
)
1684+
report.generate()
1685+
1686+
context.update(
1687+
{
1688+
"advertiser": advertiser,
1689+
"report": report,
1690+
"flights": Flight.objects.filter(
1691+
campaign__advertiser=advertiser
1692+
).order_by("-start_date"),
1693+
}
1694+
)
1695+
1696+
return context
1697+
1698+
16421699
class StaffAdvertiserReportView(BaseReportView):
16431700
"""A report aggregating all advertisers."""
16441701

‎package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ethical-ad-server",
3-
"version": "5.12.0",
3+
"version": "5.13.0",
44
"description": "",
55
"main": "index.js",
66
"engines": {

‎requirements/analyzer.txt

+12-12
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,16 @@ catalogue==2.0.10
1818
# srsly
1919
# textacy
2020
# thinc
21-
certifi==2024.8.30
21+
certifi==2024.12.14
2222
# via
2323
# requests
2424
# trafilatura
25-
charset-normalizer==3.4.0
25+
charset-normalizer==3.4.1
2626
# via
2727
# htmldate
2828
# requests
2929
# trafilatura
30-
click==8.1.7
30+
click==8.1.8
3131
# via
3232
# nltk
3333
# typer
@@ -42,7 +42,7 @@ cymem==2.0.10
4242
# preshed
4343
# spacy
4444
# thinc
45-
cytoolz==1.0.0
45+
cytoolz==1.0.1
4646
# via textacy
4747
dateparser==1.2.0
4848
# via htmldate
@@ -55,11 +55,11 @@ filelock==3.16.1
5555
# transformers
5656
floret==0.10.5
5757
# via textacy
58-
fsspec==2024.10.0
58+
fsspec==2024.12.0
5959
# via
6060
# huggingface-hub
6161
# torch
62-
htmldate==1.9.2
62+
htmldate==1.9.3
6363
# via trafilatura
6464
huggingface-hub==0.24.7
6565
# via
@@ -68,9 +68,9 @@ huggingface-hub==0.24.7
6868
# transformers
6969
idna==3.10
7070
# via requests
71-
jellyfish==1.1.2
71+
jellyfish==1.1.3
7272
# via textacy
73-
jinja2==3.1.4
73+
jinja2==3.1.5
7474
# via
7575
# spacy
7676
# torch
@@ -173,7 +173,7 @@ pathy==0.11.0
173173
# via spacy
174174
pgvector==0.3.6
175175
# via -r analyzer.in
176-
pillow==11.0.0
176+
pillow==11.1.0
177177
# via torchvision
178178
preshed==3.0.9
179179
# via
@@ -209,7 +209,7 @@ requests==2.32.3
209209
# textacy
210210
# transformers
211211
# weasel
212-
scikit-learn==1.5.2
212+
scikit-learn==1.6.0
213213
# via
214214
# sentence-transformers
215215
# textacy
@@ -247,7 +247,7 @@ spacy-loggers==1.0.5
247247
# via spacy
248248
spacy-transformers==1.1.9
249249
# via -r analyzer.in
250-
srsly==2.4.8
250+
srsly==2.5.0
251251
# via
252252
# confection
253253
# spacy
@@ -304,7 +304,7 @@ typing-extensions==4.12.2
304304
# torch
305305
tzlocal==5.2
306306
# via dateparser
307-
urllib3==2.2.3
307+
urllib3==2.3.0
308308
# via
309309
# courlan
310310
# htmldate

‎requirements/base.in

+4
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,7 @@ django-cors-headers
6565

6666
# Deprecated, but still used in migrations
6767
jsonfield
68+
69+
# Used until UUID7 is in the Python stdlib (3.14 or 3.15)
70+
# https://github.com/python/cpython/pull/121119
71+
uuid_utils >=0.9.0, <1.0.0

‎requirements/base.txt

+15-15
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
#
77
aiohappyeyeballs==2.4.4
88
# via aiohttp
9-
aiohttp==3.11.9
9+
aiohttp==3.11.11
1010
# via geoip2
11-
aiosignal==1.3.1
11+
aiosignal==1.3.2
1212
# via aiohttp
1313
amqp==5.3.1
1414
# via kombu
@@ -22,19 +22,19 @@ async-timeout==5.0.1
2222
# via
2323
# aiohttp
2424
# redis
25-
attrs==24.2.0
25+
attrs==24.3.0
2626
# via aiohttp
2727
billiard==4.2.1
2828
# via celery
2929
bleach==6.2.0
3030
# via -r base.in
3131
celery[redis]==5.5.0rc1
3232
# via -r base.in
33-
certifi==2024.8.30
33+
certifi==2024.12.14
3434
# via requests
35-
charset-normalizer==3.4.0
35+
charset-normalizer==3.4.1
3636
# via requests
37-
click==8.1.7
37+
click==8.1.8
3838
# via
3939
# celery
4040
# click-didyoumean
@@ -63,7 +63,7 @@ django==5.0.10
6363
# django-slack
6464
# djangorestframework
6565
# jsonfield
66-
django-allauth==65.3.0
66+
django-allauth==65.3.1
6767
# via -r base.in
6868
django-cors-headers==4.6.0
6969
# via -r base.in
@@ -111,7 +111,7 @@ multidict==6.1.0
111111
# via
112112
# aiohttp
113113
# yarl
114-
pillow==11.0.0
114+
pillow==11.1.0
115115
# via -r base.in
116116
prompt-toolkit==3.0.48
117117
# via click-repl
@@ -125,7 +125,7 @@ python-dateutil==2.9.0.post0
125125
# via celery
126126
pytz==2024.2
127127
# via -r base.in
128-
redis==5.2.0
128+
redis==5.2.1
129129
# via celery
130130
requests==2.32.3
131131
# via
@@ -134,7 +134,7 @@ requests==2.32.3
134134
# stripe
135135
six==1.17.0
136136
# via python-dateutil
137-
sqlparse==0.5.2
137+
sqlparse==0.5.3
138138
# via django
139139
stripe==4.2.0
140140
# via
@@ -148,15 +148,15 @@ typing-extensions==4.12.2
148148
tzdata==2024.2
149149
# via kombu
150150
ua-parser==1.0.0
151-
# via
152-
# ua-parser-builtins
153-
# user-agents
154-
ua-parser-builtins==0.18.0
151+
# via user-agents
152+
ua-parser-builtins==0.18.0.post1
155153
# via ua-parser
156-
urllib3==2.2.3
154+
urllib3==2.3.0
157155
# via requests
158156
user-agents==2.2.0
159157
# via -r base.in
158+
uuid-utils==0.10.0
159+
# via -r base.in
160160
vine==5.1.0
161161
# via
162162
# amqp

‎requirements/development.txt

+23-23
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66
#
77
aiohappyeyeballs==2.4.4
88
# via aiohttp
9-
aiohttp==3.11.9
9+
aiohttp==3.11.11
1010
# via geoip2
11-
aiosignal==1.3.1
11+
aiosignal==1.3.2
1212
# via aiohttp
1313
alabaster==0.7.16
1414
# via sphinx
1515
amqp==5.3.1
1616
# via kombu
17-
anyio==4.6.2.post1
17+
anyio==4.7.0
1818
# via
1919
# starlette
2020
# watchfiles
@@ -30,7 +30,7 @@ async-timeout==5.0.1
3030
# via
3131
# aiohttp
3232
# redis
33-
attrs==24.2.0
33+
attrs==24.3.0
3434
# via
3535
# aiohttp
3636
# cattrs
@@ -47,13 +47,13 @@ cattrs==24.1.2
4747
# via requests-cache
4848
celery[redis]==5.5.0rc1
4949
# via -r base.in
50-
certifi==2024.8.30
50+
certifi==2024.12.14
5151
# via requests
5252
cfgv==3.4.0
5353
# via pre-commit
54-
charset-normalizer==3.4.0
54+
charset-normalizer==3.4.1
5555
# via requests
56-
click==8.1.7
56+
click==8.1.8
5757
# via
5858
# celery
5959
# click-didyoumean
@@ -68,7 +68,7 @@ click-repl==0.3.0
6868
# via celery
6969
colorama==0.4.6
7070
# via sphinx-autobuild
71-
coverage==7.6.8
71+
coverage==7.6.10
7272
# via
7373
# -r development.in
7474
# django-coverage-plugin
@@ -96,7 +96,7 @@ django==5.0.10
9696
# django-slack
9797
# djangorestframework
9898
# jsonfield
99-
django-allauth==65.3.0
99+
django-allauth==65.3.1
100100
# via -r base.in
101101
django-cors-headers==4.6.0
102102
# via -r base.in
@@ -154,7 +154,7 @@ geoip2==4.8.1
154154
# via -r base.in
155155
h11==0.14.0
156156
# via uvicorn
157-
identify==2.6.3
157+
identify==2.6.4
158158
# via pre-commit
159159
idna==3.10
160160
# via
@@ -169,13 +169,13 @@ ip2proxy==3.4.2
169169
# via -r base.in
170170
ipdb==0.13.13
171171
# via -r development.in
172-
ipython==8.30.0
172+
ipython==8.31.0
173173
# via
174174
# -r development.in
175175
# ipdb
176176
jedi==0.19.2
177177
# via ipython
178-
jinja2==3.1.4
178+
jinja2==3.1.5
179179
# via sphinx
180180
jsonfield==3.1.0
181181
# via -r base.in
@@ -202,7 +202,7 @@ parso==0.8.4
202202
# via jedi
203203
pexpect==4.9.0
204204
# via ipython
205-
pillow==11.0.0
205+
pillow==11.1.0
206206
# via -r base.in
207207
platformdirs==4.3.6
208208
# via
@@ -248,7 +248,7 @@ pyyaml==6.0.2
248248
# via
249249
# pre-commit
250250
# responses
251-
redis==5.2.0
251+
redis==5.2.1
252252
# via celery
253253
requests==2.32.3
254254
# via
@@ -303,13 +303,13 @@ sphinxcontrib-qthelp==2.0.0
303303
# via sphinx
304304
sphinxcontrib-serializinghtml==2.0.0
305305
# via sphinx
306-
sqlparse==0.5.2
306+
sqlparse==0.5.3
307307
# via
308308
# django
309309
# django-debug-toolbar
310310
stack-data==0.6.3
311311
# via ipython
312-
starlette==0.41.3
312+
starlette==0.45.1
313313
# via sphinx-autobuild
314314
stripe==4.2.0
315315
# via
@@ -340,22 +340,22 @@ typing-extensions==4.12.2
340340
tzdata==2024.2
341341
# via kombu
342342
ua-parser==1.0.0
343-
# via
344-
# ua-parser-builtins
345-
# user-agents
346-
ua-parser-builtins==0.18.0
343+
# via user-agents
344+
ua-parser-builtins==0.18.0.post1
347345
# via ua-parser
348346
url-normalize==1.4.3
349347
# via requests-cache
350-
urllib3==2.2.3
348+
urllib3==2.3.0
351349
# via
352350
# -r development.in
353351
# requests
354352
# requests-cache
355353
# responses
356354
user-agents==2.2.0
357355
# via -r base.in
358-
uvicorn==0.32.1
356+
uuid-utils==0.10.0
357+
# via -r base.in
358+
uvicorn==0.34.0
359359
# via sphinx-autobuild
360360
vine==5.1.0
361361
# via
@@ -366,7 +366,7 @@ virtualenv==20.28.0
366366
# via
367367
# pre-commit
368368
# tox
369-
watchfiles==1.0.0
369+
watchfiles==1.0.3
370370
# via sphinx-autobuild
371371
wcwidth==0.2.13
372372
# via prompt-toolkit

‎requirements/production.txt

+17-17
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
#
77
aiohappyeyeballs==2.4.4
88
# via aiohttp
9-
aiohttp==3.11.9
9+
aiohttp==3.11.11
1010
# via geoip2
11-
aiosignal==1.3.1
11+
aiosignal==1.3.2
1212
# via aiohttp
1313
amqp==5.3.1
1414
# via kombu
@@ -22,7 +22,7 @@ async-timeout==5.0.1
2222
# via
2323
# aiohttp
2424
# redis
25-
attrs==24.2.0
25+
attrs==24.3.0
2626
# via aiohttp
2727
azure-core==1.32.0
2828
# via
@@ -36,15 +36,15 @@ bleach==6.2.0
3636
# via -r base.in
3737
celery[redis]==5.5.0rc1
3838
# via -r base.in
39-
certifi==2024.8.30
39+
certifi==2024.12.14
4040
# via
4141
# requests
4242
# sentry-sdk
4343
cffi==1.17.1
4444
# via cryptography
45-
charset-normalizer==3.4.0
45+
charset-normalizer==3.4.1
4646
# via requests
47-
click==8.1.7
47+
click==8.1.8
4848
# via
4949
# celery
5050
# click-didyoumean
@@ -78,7 +78,7 @@ django==5.0.10
7878
# django-storages
7979
# djangorestframework
8080
# jsonfield
81-
django-allauth==65.3.0
81+
django-allauth==65.3.1
8282
# via -r base.in
8383
django-anymail==12.0
8484
# via -r production.in
@@ -136,11 +136,11 @@ multidict==6.1.0
136136
# via
137137
# aiohttp
138138
# yarl
139-
newrelic==10.3.1
139+
newrelic==10.4.0
140140
# via -r production.in
141141
packaging==24.2
142142
# via gunicorn
143-
pillow==11.0.0
143+
pillow==11.1.0
144144
# via -r base.in
145145
prompt-toolkit==3.0.48
146146
# via click-repl
@@ -158,7 +158,7 @@ python-dateutil==2.9.0.post0
158158
# via celery
159159
pytz==2024.2
160160
# via -r base.in
161-
redis==5.2.0
161+
redis==5.2.1
162162
# via
163163
# celery
164164
# django-redis
@@ -169,13 +169,13 @@ requests==2.32.3
169169
# django-slack
170170
# geoip2
171171
# stripe
172-
sentry-sdk==2.19.0
172+
sentry-sdk==2.19.2
173173
# via -r production.in
174174
six==1.17.0
175175
# via
176176
# azure-core
177177
# python-dateutil
178-
sqlparse==0.5.2
178+
sqlparse==0.5.3
179179
# via django
180180
stripe==4.2.0
181181
# via
@@ -191,18 +191,18 @@ typing-extensions==4.12.2
191191
tzdata==2024.2
192192
# via kombu
193193
ua-parser==1.0.0
194-
# via
195-
# ua-parser-builtins
196-
# user-agents
197-
ua-parser-builtins==0.18.0
194+
# via user-agents
195+
ua-parser-builtins==0.18.0.post1
198196
# via ua-parser
199-
urllib3==2.2.3
197+
urllib3==2.3.0
200198
# via
201199
# django-anymail
202200
# requests
203201
# sentry-sdk
204202
user-agents==2.2.0
205203
# via -r base.in
204+
uuid-utils==0.10.0
205+
# via -r base.in
206206
vine==5.1.0
207207
# via
208208
# amqp

0 commit comments

Comments
 (0)
Please sign in to comment.