Skip to content

Commit 70f0a10

Browse files
authored
Model stuff (#217)
* Add unique bill vote constraint * Changed submit_task to use context manager * Delete and recreate bill votes * Fix mypy errors * Make interaction links model methods * back to 💯
1 parent 013e39c commit 70f0a10

10 files changed

Lines changed: 180 additions & 64 deletions

File tree

democrasite/activitypub/models.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ def get_absolute_url(self):
6262
"activitypub:person-detail", kwargs={"username": self.display_name}
6363
)
6464

65+
def get_follow_url(self):
66+
return reverse(
67+
"activitypub:person-follow", kwargs={"username": self.display_name}
68+
)
69+
6570
def is_following(self, person: "Person") -> bool:
6671
"""Check if a person is following this person.
6772
@@ -117,12 +122,12 @@ def __str__(self):
117122
return f'{self.person} reposted "{self.note}"'
118123

119124

120-
class NoteManager[T](TreeManager):
121-
def get_queryset(self) -> models.QuerySet[T]:
125+
class NoteManager(TreeManager):
126+
def get_queryset(self) -> models.QuerySet:
122127
"""Get the queryset for notes, ordered by creation date."""
123128
return super().get_queryset().order_by(*self.model._meta.ordering) # noqa: SLF001
124129

125-
def get_person_notes(self, person: Person) -> models.QuerySet[T]:
130+
def get_person_notes(self, person: Person) -> models.QuerySet:
126131
"""Get notes for display on a person's profile page.
127132
128133
This method returns all notes authored by the person as well as all their
@@ -150,7 +155,7 @@ def get_person_notes(self, person: Person) -> models.QuerySet[T]:
150155
)
151156
return posts.union(reposts).order_by("-order_time")
152157

153-
def get_person_following_notes(self, person: Person) -> models.QuerySet[T]:
158+
def get_person_following_notes(self, person: Person) -> models.QuerySet:
154159
"""Get notes from people the person is following.
155160
156161
This method retrieves all notes authored or reposted by people that the
@@ -240,6 +245,9 @@ def get_absolute_url(self):
240245
"""
241246
return reverse("activitypub:note-detail", kwargs={"pk": self.pk})
242247

248+
def get_like_url(self):
249+
return reverse("activitypub:note-like", kwargs={"pk": self.id})
250+
243251
def liked_by(self, person: Person) -> bool:
244252
"""Check if a person has liked the note.
245253
@@ -264,6 +272,9 @@ def like(self, person: Person) -> bool:
264272
self.likes.add(person)
265273
return True
266274

275+
def get_repost_url(self):
276+
return reverse("activitypub:note-repost", kwargs={"pk": self.id})
277+
267278
def reposted_by(self, person: Person) -> bool:
268279
"""Check if a person has reposted the note.
269280

democrasite/activitypub/tests/test_models.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ def test_get_absolute_url(self, person: Person):
2727
person.get_absolute_url() == f"/activitypub/person/{person.user.username}/"
2828
)
2929

30+
def test_get_follow_url(self, person: Person):
31+
assert (
32+
person.get_follow_url()
33+
== f"/activitypub/person/{person.user.username}/follow/"
34+
)
35+
3036
def test_is_following(self, person: Person):
3137
person2 = PersonFactory.create()
3238
person.following.add(person2)
@@ -81,7 +87,7 @@ def test_get_person_notes(self, person: Person, note: Note):
8187

8288
repost = person_notes[1]
8389
assert repost.repost_person == person.display_name
84-
assert repost.repost_time == note.repost_set.first().created
90+
assert repost.repost_time == note.repost_set.first().created # type: ignore[union-attr]
8591

8692
def test_get_person_following_notes(self, person: Person):
8793
person2 = PersonFactory.create()
@@ -104,7 +110,7 @@ def test_get_person_following_notes(self, person: Person):
104110

105111
repost = following_notes[1]
106112
assert repost.repost_person == person2.display_name
107-
assert repost.repost_time == own_notes[0].repost_set.first().created
113+
assert repost.repost_time == own_notes[0].repost_set.first().created # type: ignore[union-attr]
108114

109115

110116
class TestNote:
@@ -119,6 +125,9 @@ def test_str_short_content(self):
119125
def test_get_absolute_url(self, note: Note):
120126
assert note.get_absolute_url() == f"/activitypub/notes/{note.pk}/"
121127

128+
def test_get_like_url(self, note: Note):
129+
assert note.get_like_url() == f"/activitypub/notes/{note.id}/like/"
130+
122131
def test_liked_by(self, note: Note, person: Person):
123132
assert not note.liked_by(person)
124133

@@ -135,6 +144,9 @@ def test_like(self, note: Note, person: Person):
135144

136145
assert person not in note.likes.all()
137146

147+
def test_get_repost_url(self, note: Note):
148+
assert note.get_repost_url() == f"/activitypub/notes/{note.id}/repost/"
149+
138150
def test_reposted_by(self, note: Note, person: Person):
139151
assert not note.reposted_by(person)
140152

democrasite/activitypub/views.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ def form_valid(self, form):
105105
@require_POST
106106
@require_user_profile
107107
def note_like_view(request: HttpRequest, pk: int) -> http.HttpResponse:
108+
assert request.user.is_authenticated # type guard
109+
108110
note = get_object_or_404(Note, pk=pk)
109111
note.like(request.user.person)
110112

@@ -114,6 +116,8 @@ def note_like_view(request: HttpRequest, pk: int) -> http.HttpResponse:
114116
@require_POST
115117
@require_user_profile
116118
def note_repost_view(request: HttpRequest, pk: int) -> http.HttpResponse:
119+
assert request.user.is_authenticated # type guard
120+
117121
note = get_object_or_404(Note, pk=pk)
118122
note.repost(request.user.person)
119123

@@ -193,6 +197,8 @@ def get_queryset(self):
193197
@require_user_profile
194198
def person_follow_view(request: HttpRequest, username: str) -> http.HttpResponse:
195199
"""Follow a user by username."""
200+
assert request.user.is_authenticated # type guard
201+
196202
person = get_object_or_404(Person, user__username=username)
197203
request.user.person.following.add(person)
198204

democrasite/templates/activitypub/snippets/note.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
bi-heart
2222
{% endif %}
2323
text-danger note-like"
24-
action="{% url 'activitypub:note-like' note.id %}"></i>
24+
action="{{ note.get_like_url }}"></i>
2525
</a>
2626
<small class="text-secondary note-like-count">{{ note.likes.count }}</small>
2727
<a href="javascript:;"
@@ -34,7 +34,7 @@
3434
text-secondary
3535
{% endif %}
3636
note-repost"
37-
action="{% url 'activitypub:note-repost' note.id %}"></i>
37+
action="{{ note.get_repost_url }}"></i>
3838
</a>
3939
<small class="text-secondary note-repost-count">{{ note.reposts.count }}</small>
4040
<a class="float-right text-secondary"

democrasite/templates/webiscite/bill_detail.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ <h2>{{ bill }}</h2>
2323
<p>
2424
{{ bill.description }}
2525
{% if bill.author == user and bill.status == bill.Status.OPEN %}
26-
<a href="{% url 'webiscite:bill-update' bill.id %}">
26+
<a href="{{ bill.get_update_url }}">
2727
<i class="bi bi-pencil-square"></i>
2828
</a>
2929
{% endif %}
@@ -50,7 +50,7 @@ <h2>{{ bill }}</h2>
5050
{% if user.is_authenticated %}
5151
{# if logged in, add vote-enabling logic #}
5252
id="vote-yes-{{ bill.id }}"
53-
action="{% url 'webiscite:bill-vote' bill.id %}"
53+
action="{{ bill.get_vote_url }}"
5454
value="vote-yes"
5555
{% else %}
5656
{# otherwise, add a "log in to vote" popup #}
@@ -66,7 +66,7 @@ <h2>{{ bill }}</h2>
6666
{% if user in bill.no_votes.all %}font-weight-bold{% endif %}"
6767
{% if bill.status == bill.Status.OPEN %}
6868
{% if user.is_authenticated %}
69-
id="vote-no-{{ bill.id }}" action="{% url 'webiscite:bill-vote' bill.id %}" value="vote-no"
69+
id="vote-no-{{ bill.id }}" action="{{ bill.get_vote_url }}" value="vote-no"
7070
{% else %}
7171
data-toggle="tooltip" data-placement="bottom" title="{% trans 'Log in to vote' %}" data-trigger="click"
7272
{% endif %}

democrasite/templates/webiscite/bill_list.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ <h5 class="card-title">{{ bill }}</h5>
4646
{% if user in bill.yes_votes.all %}font-weight-bold{% endif %}"
4747
{% if bill.status == bill.Status.OPEN %}
4848
{% if user.is_authenticated %}
49-
id="vote-yes-{{ bill.id }}" action="{% url 'webiscite:bill-vote' bill.id %}" value="vote-yes"
49+
id="vote-yes-{{ bill.id }}" action="{{ bill.get_vote_url }}" value="vote-yes"
5050
{% else %}
5151
data-toggle="tooltip" data-placement="bottom" title="Log in to vote" data-trigger="click"
5252
{% endif %}
@@ -58,7 +58,7 @@ <h5 class="card-title">{{ bill }}</h5>
5858
{% if user in bill.no_votes.all %}font-weight-bold{% endif %}"
5959
{% if bill.status == bill.Status.OPEN %}
6060
{% if user.is_authenticated %}
61-
id="vote-no-{{ bill.id }}" action="{% url 'webiscite:bill-vote' bill.id %}" value="vote-no"
61+
id="vote-no-{{ bill.id }}" action="{{ bill.get_vote_url }}" value="vote-no"
6262
{% else %}
6363
data-toggle="tooltip" data-placement="bottom" title="{% trans 'Log in to vote' %}" data-trigger="click"
6464
{% endif %}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Generated by Django 5.1.6 on 2025-07-05 19:13
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('webiscite', '0002_remove_bill_submit_task_bill__submit_task'),
11+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12+
]
13+
14+
operations = [
15+
migrations.AddConstraint(
16+
model_name='vote',
17+
constraint=models.UniqueConstraint(fields=('bill', 'user'), name='unique_user_bill_vote', violation_error_code='Only one vote per user per bill allowed'),
18+
),
19+
]

0 commit comments

Comments
 (0)