Compare commits
17 Commits
main
...
ci-test-co
Author | SHA1 | Date | |
---|---|---|---|
c9f46d7547 | |||
9f23f5768c | |||
19210f90cd | |||
462bb8f485 | |||
ea4d15b99a | |||
de30dfcb8b | |||
36a979954c | |||
71ef17dc97 | |||
206cd282e6 | |||
e399346c3e | |||
929c6dfff0 | |||
841b57fea2 | |||
9e5446ff1d | |||
3b79809b8c | |||
53e6db3655 | |||
424f91e919 | |||
84ce5f54b2 |
4
.coveragerc
Normal file
4
.coveragerc
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[run]
|
||||||
|
omit =
|
||||||
|
*/migrations/*
|
||||||
|
*/tests/*
|
@ -6,6 +6,9 @@ steps:
|
|||||||
commands:
|
commands:
|
||||||
- cd docs && make html
|
- cd docs && make html
|
||||||
|
|
||||||
|
when:
|
||||||
|
event: [ tag, push ]
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
image: appleboy/drone-scp
|
image: appleboy/drone-scp
|
||||||
settings:
|
settings:
|
||||||
@ -19,6 +22,8 @@ steps:
|
|||||||
source: docs/_build/html/
|
source: docs/_build/html/
|
||||||
key:
|
key:
|
||||||
from_secret: ssh_key
|
from_secret: ssh_key
|
||||||
|
when:
|
||||||
|
event: [ tag, push ]
|
||||||
|
|
||||||
|
|
||||||
|
|
14
.woodpecker/test.yml
Normal file
14
.woodpecker/test.yml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
steps:
|
||||||
|
test:
|
||||||
|
image: python
|
||||||
|
commands:
|
||||||
|
- python -m pip install '.[develop]'
|
||||||
|
- coverage run --source='.' src/manage.py test src && coverage html
|
||||||
|
- coverage html
|
||||||
|
- cat htmlcov/index.html
|
||||||
|
when:
|
||||||
|
event: [tag, push]
|
||||||
|
|
||||||
|
|
20
README.md
20
README.md
@ -77,6 +77,26 @@ docker push moanos/notfellchen:latest
|
|||||||
docker run -p8000:7345 moanos/notfellchen:latest
|
docker run -p8000:7345 moanos/notfellchen:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Tests can be run with
|
||||||
|
|
||||||
|
```zsh
|
||||||
|
nf test src
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to report on code coverage run
|
||||||
|
|
||||||
|
```zsh
|
||||||
|
coverage run --source='.' src/manage.py test src
|
||||||
|
```
|
||||||
|
|
||||||
|
and
|
||||||
|
|
||||||
|
```
|
||||||
|
coverage report
|
||||||
|
```
|
||||||
|
|
||||||
## Geocoding
|
## Geocoding
|
||||||
|
|
||||||
Geocoding services (search map data by name, address or postcode) are provided via the
|
Geocoding services (search map data by name, address or postcode) are provided via the
|
||||||
|
@ -36,6 +36,11 @@ An application can then send this token in the request header for authorization.
|
|||||||
Endpoints
|
Endpoints
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
All Endpoints are documented at https://notfellchen.org/api/schema/swagger-ui/ or at https://notfellchen.org/api/schema/redoc/ if you prefer redoc.
|
||||||
|
The OpenAI schema can be downloaded at https://notfellchen.org/api/schema/
|
||||||
|
|
||||||
|
Examples are documented here.
|
||||||
|
|
||||||
Get Adoption Notices
|
Get Adoption Notices
|
||||||
++++++++++++++++++++
|
++++++++++++++++++++
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ Report a bug
|
|||||||
^^^^^^^^^^^^
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
To report a bug, file an issue on `Github
|
To report a bug, file an issue on `Github
|
||||||
<https://codeberg.org/moanos/notfellchen/issues>`_
|
<https://github.com/moan0s/notfellchen/issues>`_
|
||||||
|
|
||||||
Try to include the following information:
|
Try to include the following information:
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ To contribute simply clone the directory, make your changes and file a
|
|||||||
pull request.
|
pull request.
|
||||||
|
|
||||||
If you want to know what can be done, have a look at the current `Github
|
If you want to know what can be done, have a look at the current `Github
|
||||||
<https://codeberg.org/moanos/notfellchen/issues>`_.
|
<https://github.com/moan0s/notfellchen/issues>`_.
|
||||||
|
|
||||||
Get in touch!
|
Get in touch!
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
|
@ -5,8 +5,7 @@ What qualifies as release?
|
|||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
A new release should be announced when a significant number functions, bugfixes or other improvements to the software
|
A new release should be announced when a significant number functions, bugfixes or other improvements to the software
|
||||||
is made. Usually this indicates a minor release.
|
is made. Notfellchen follows `Semantic Versioning <https://semver.org/>`_.
|
||||||
Major releases are yet to be determined.
|
|
||||||
|
|
||||||
What should be done before a release?
|
What should be done before a release?
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@ -14,7 +13,7 @@ What should be done before a release?
|
|||||||
Tested basic functions
|
Tested basic functions
|
||||||
######################
|
######################
|
||||||
|
|
||||||
Run :command:`pytest`
|
Run :command:`nf test src`
|
||||||
|
|
||||||
Test upgrade on a copy of a production database
|
Test upgrade on a copy of a production database
|
||||||
###############################################
|
###############################################
|
||||||
@ -38,4 +37,4 @@ Do a final commit on this change, and tag the commit as release with appropriate
|
|||||||
git tag -a v1.0.0 -m "Releasing version v1.0.0"
|
git tag -a v1.0.0 -m "Releasing version v1.0.0"
|
||||||
git push origin v1.0.0
|
git push origin v1.0.0
|
||||||
|
|
||||||
Make sure the tag is visible on Codeberg and celebrate 🥳
|
Make sure the tag is visible on GitHub/Codeberg and celebrate 🥳
|
||||||
|
@ -8,13 +8,13 @@ build-backend = "setuptools.build_meta"
|
|||||||
name = "notfellchen"
|
name = "notfellchen"
|
||||||
description = "A tool to help."
|
description = "A tool to help."
|
||||||
authors = [
|
authors = [
|
||||||
{name = "moanos", email = "julian-samuel@gebuehr.net"},
|
{ name = "moanos", email = "julian-samuel@gebuehr.net" },
|
||||||
]
|
]
|
||||||
maintainers = [
|
maintainers = [
|
||||||
{name = "moanos", email = "julian-samuel@gebuehr.net"},
|
{ name = "moanos", email = "julian-samuel@gebuehr.net" },
|
||||||
]
|
]
|
||||||
keywords = ["animal", "adoption", "django", "rescue", ]
|
keywords = ["animal", "adoption", "django", "rescue", ]
|
||||||
license = {text = "AGPL-3.0-or-later"}
|
license = { text = "AGPL-3.0-or-later" }
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Environment :: Web",
|
"Environment :: Web",
|
||||||
"License :: OSI Approved :: GNU Affero General Public License v3",
|
"License :: OSI Approved :: GNU Affero General Public License v3",
|
||||||
@ -24,14 +24,12 @@ classifiers = [
|
|||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"Django",
|
"Django",
|
||||||
"coverage",
|
|
||||||
"codecov",
|
"codecov",
|
||||||
"sphinx",
|
"sphinx",
|
||||||
"sphinx-rtd-theme",
|
"sphinx-rtd-theme",
|
||||||
"gunicorn",
|
"gunicorn",
|
||||||
"fontawesomefree",
|
"fontawesomefree",
|
||||||
"whitenoise",
|
"whitenoise",
|
||||||
"model_bakery",
|
|
||||||
"markdown",
|
"markdown",
|
||||||
"Pillow",
|
"Pillow",
|
||||||
"django-registration",
|
"django-registration",
|
||||||
@ -48,6 +46,8 @@ dynamic = ["version", "readme"]
|
|||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
develop = [
|
develop = [
|
||||||
"pytest",
|
"pytest",
|
||||||
|
"coverage",
|
||||||
|
"model_bakery",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
@ -62,6 +62,6 @@ nf = 'notfellchen.main:main'
|
|||||||
|
|
||||||
|
|
||||||
[tool.setuptools.dynamic]
|
[tool.setuptools.dynamic]
|
||||||
version = {attr = "notfellchen.__version__"}
|
version = { attr = "notfellchen.__version__" }
|
||||||
readme = {file = "README.md"}
|
readme = { file = "README.md" }
|
||||||
|
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2025-01-14 06:28
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0035_alter_image_alt_text_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='basenotification',
|
||||||
|
name='read_at',
|
||||||
|
field=models.DateTimeField(blank=True, null=True, verbose_name='Gelesen am'),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2025-01-14 06:28
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fellchensammlung', '0036_basenotification_read_at'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='basenotification',
|
||||||
|
name='title',
|
||||||
|
field=models.CharField(max_length=100, verbose_name='Titel'),
|
||||||
|
),
|
||||||
|
]
|
@ -818,7 +818,8 @@ class Comment(models.Model):
|
|||||||
class BaseNotification(models.Model):
|
class BaseNotification(models.Model):
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
title = models.CharField(max_length=100)
|
read_at = models.DateTimeField(blank=True, null=True, verbose_name=_("Gelesen am"))
|
||||||
|
title = models.CharField(max_length=100, verbose_name=_("Titel"))
|
||||||
text = models.TextField(verbose_name="Inhalt")
|
text = models.TextField(verbose_name="Inhalt")
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('Nutzer*in'))
|
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('Nutzer*in'))
|
||||||
read = models.BooleanField(default=False)
|
read = models.BooleanField(default=False)
|
||||||
@ -829,6 +830,11 @@ class BaseNotification(models.Model):
|
|||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
self.user.get_notifications_url()
|
self.user.get_notifications_url()
|
||||||
|
|
||||||
|
def mark_read(self):
|
||||||
|
self.read = True
|
||||||
|
self.read_at = timezone.now()
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
class CommentNotification(BaseNotification):
|
class CommentNotification(BaseNotification):
|
||||||
comment = models.ForeignKey(Comment, on_delete=models.CASCADE, verbose_name=_('Antwort'))
|
comment = models.ForeignKey(Comment, on_delete=models.CASCADE, verbose_name=_('Antwort'))
|
||||||
|
@ -259,6 +259,11 @@ a.btn, a.btn2, a.nav-link {
|
|||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-small {
|
||||||
|
font-size: medium;
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
.checkmark {
|
.checkmark {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -7,7 +7,7 @@ from .mail import send_notification_email
|
|||||||
from .tools.admin import clean_locations, deactivate_unchecked_adoption_notices, deactivate_404_adoption_notices
|
from .tools.admin import clean_locations, deactivate_unchecked_adoption_notices, deactivate_404_adoption_notices
|
||||||
from .tools.misc import healthcheck_ok
|
from .tools.misc import healthcheck_ok
|
||||||
from .models import Location, AdoptionNotice, Timestamp
|
from .models import Location, AdoptionNotice, Timestamp
|
||||||
from .tools.notifications import notify_moderators_of_AN_to_be_checked
|
from .tools.notifications import notify_of_AN_to_be_checked
|
||||||
from .tools.search import notify_search_subscribers
|
from .tools.search import notify_search_subscribers
|
||||||
|
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ def post_adoption_notice_save(pk):
|
|||||||
logging.info(f"Location was added to Adoption notice {pk}")
|
logging.info(f"Location was added to Adoption notice {pk}")
|
||||||
|
|
||||||
notify_search_subscribers(instance, only_if_active=True)
|
notify_search_subscribers(instance, only_if_active=True)
|
||||||
notify_moderators_of_AN_to_be_checked(instance)
|
notify_of_AN_to_be_checked(instance)
|
||||||
|
|
||||||
@celery_app.task(name="tools.healthcheck")
|
@celery_app.task(name="tools.healthcheck")
|
||||||
def task_healthcheck():
|
def task_healthcheck():
|
||||||
|
@ -137,7 +137,6 @@ class GeoAPI:
|
|||||||
result = self.requests.get(self.api_url,
|
result = self.requests.get(self.api_url,
|
||||||
{"q": location_string, "lang": language},
|
{"q": location_string, "lang": language},
|
||||||
headers=self.headers).json()
|
headers=self.headers).json()
|
||||||
logging.warning(result)
|
|
||||||
geofeatures = GeoFeature.geofeatures_from_photon_result(result)
|
geofeatures = GeoFeature.geofeatures_from_photon_result(result)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
from fellchensammlung.models import User, AdoptionNoticeNotification, TrustLevel
|
from fellchensammlung.models import User, AdoptionNoticeNotification, TrustLevel
|
||||||
|
|
||||||
|
|
||||||
def notify_moderators_of_AN_to_be_checked(adoption_notice):
|
def notify_of_AN_to_be_checked(adoption_notice):
|
||||||
if adoption_notice.is_disabled_unchecked:
|
if adoption_notice.is_disabled_unchecked:
|
||||||
for moderator in User.objects.filter(trust_level__gt=TrustLevel.MODERATOR):
|
users_to_notify = set(User.objects.filter(trust_level__gt=TrustLevel.MODERATOR))
|
||||||
|
users_to_notify.add(adoption_notice.owner)
|
||||||
|
for user in users_to_notify:
|
||||||
AdoptionNoticeNotification.objects.create(adoption_notice=adoption_notice,
|
AdoptionNoticeNotification.objects.create(adoption_notice=adoption_notice,
|
||||||
user=moderator,
|
user=user,
|
||||||
title=f" Prüfe Vermittlung {adoption_notice}",
|
title=f" Prüfe Vermittlung {adoption_notice}",
|
||||||
text=f"{adoption_notice} muss geprüft werden bevor sie veröffentlicht wird.",
|
text=f"{adoption_notice} muss geprüft werden bevor sie veröffentlicht wird.",
|
||||||
)
|
)
|
@ -130,6 +130,8 @@ def adoption_notice_detail(request, adoption_notice_id):
|
|||||||
if action == "unsubscribe":
|
if action == "unsubscribe":
|
||||||
subscription.delete()
|
subscription.delete()
|
||||||
is_subscribed = False
|
is_subscribed = False
|
||||||
|
elif action == "subscribe":
|
||||||
|
return redirect_to_login(next=request.path)
|
||||||
else:
|
else:
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
else:
|
else:
|
||||||
@ -481,13 +483,11 @@ def my_profile(request):
|
|||||||
notification = CommentNotification.objects.get(pk=notification_id)
|
notification = CommentNotification.objects.get(pk=notification_id)
|
||||||
except CommentNotification.DoesNotExist:
|
except CommentNotification.DoesNotExist:
|
||||||
notification = BaseNotification.objects.get(pk=notification_id)
|
notification = BaseNotification.objects.get(pk=notification_id)
|
||||||
notification.read = True
|
notification.mark_read()
|
||||||
notification.save()
|
|
||||||
elif action == "notification_mark_all_read":
|
elif action == "notification_mark_all_read":
|
||||||
notifications = CommentNotification.objects.filter(user=request.user, mark_read=False)
|
notifications = CommentNotification.objects.filter(user=request.user, mark_read=False)
|
||||||
for notification in notifications:
|
for notification in notifications:
|
||||||
notification.read = True
|
notification.mark_read()
|
||||||
notification.save()
|
|
||||||
elif action == "search_subscription_delete":
|
elif action == "search_subscription_delete":
|
||||||
search_subscription_id = request.POST.get("search_subscription_id")
|
search_subscription_id = request.POST.get("search_subscription_id")
|
||||||
SearchSubscription.objects.get(pk=search_subscription_id).delete()
|
SearchSubscription.objects.get(pk=search_subscription_id).delete()
|
||||||
|
@ -7,21 +7,28 @@
|
|||||||
<p>{% translate "Dein Username oder Passwort ist falsch." %}</p>
|
<p>{% translate "Dein Username oder Passwort ist falsch." %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<p>{% translate "Du bist bereits eingeloggt." %}</p>
|
<p>{% translate "Du bist bereits eingeloggt." %}</p>
|
||||||
{% else %} {% if next %}
|
{% else %} {% if next %}
|
||||||
<p>{% translate "Bitte log dich ein um diese Seite sehen zu können." %}</p>
|
<p class="card">{% translate "Bitte log dich ein um diese Seite sehen zu können." %}</p>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if not user.is_authenticated %}
|
{% if not user.is_authenticated %}
|
||||||
<form class="card" method="post" action="{% url 'login' %}">
|
<div class="card">
|
||||||
|
<div class="container-edit-buttons">
|
||||||
|
<form method="post" action="{% url 'login' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.as_p }}
|
{{ form.as_p }}
|
||||||
<input class="btn" type="submit" value={% translate "Einloggen" %} />
|
<input class="btn" type="submit" value="{% translate 'Einloggen' %}"/>
|
||||||
<input type="hidden" name="next" value="{{ next }}" />
|
<input type="hidden" name="next" value="{{ next }}"/>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p><a class="btn2" href="{% url 'password_reset' %}">{% translate "Passwort vergessen?" %}</a></p>
|
<div class="container-edit-buttons">
|
||||||
{% endif %}
|
<a class="btn btn-small" href="{% url 'password_reset' %}">{% translate "Passwort vergessen?" %}</a>
|
||||||
|
<a class="btn btn-small" href="{% url 'django_registration_register' %}">{% translate "Registrieren" %}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -27,7 +27,6 @@ class DistanceTest(TestCase):
|
|||||||
l_stuttgart = LocationProxy("Stuttgart")
|
l_stuttgart = LocationProxy("Stuttgart")
|
||||||
l_tue = LocationProxy("Tübingen")
|
l_tue = LocationProxy("Tübingen")
|
||||||
# Should be 30km
|
# Should be 30km
|
||||||
print(f"{l_stuttgart.position} -> {l_tue.position}")
|
|
||||||
distance_tue_stuttgart = calculate_distance_between_coordinates(l_stuttgart.position, l_tue.position)
|
distance_tue_stuttgart = calculate_distance_between_coordinates(l_stuttgart.position, l_tue.position)
|
||||||
self.assertLess(distance_tue_stuttgart, 50)
|
self.assertLess(distance_tue_stuttgart, 50)
|
||||||
self.assertGreater(distance_tue_stuttgart, 20)
|
self.assertGreater(distance_tue_stuttgart, 20)
|
||||||
|
@ -4,7 +4,7 @@ from django.utils import timezone
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from model_bakery import baker
|
from model_bakery import baker
|
||||||
|
|
||||||
from fellchensammlung.models import Announcement, Language, User, TrustLevel
|
from fellchensammlung.models import Announcement, Language, User, TrustLevel, BaseNotification
|
||||||
|
|
||||||
|
|
||||||
class UserTest(TestCase):
|
class UserTest(TestCase):
|
||||||
@ -77,3 +77,21 @@ class AnnouncementTest(TestCase):
|
|||||||
self.assertTrue(self.announcement2 not in active_announcements)
|
self.assertTrue(self.announcement2 not in active_announcements)
|
||||||
self.assertTrue(self.announcement4 not in active_announcements)
|
self.assertTrue(self.announcement4 not in active_announcements)
|
||||||
self.assertTrue(self.announcement5 in active_announcements)
|
self.assertTrue(self.announcement5 in active_announcements)
|
||||||
|
|
||||||
|
|
||||||
|
class TestNotifications(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
cls.test_user_1 = User.objects.create(username="Testuser1", password="SUPERSECRET", email="test@example.org")
|
||||||
|
|
||||||
|
def test_mark_read(self):
|
||||||
|
not1 = BaseNotification.objects.create(user=self.test_user_1, text="New rats to adopt", title="🔔 New Rat alert")
|
||||||
|
not2 = BaseNotification.objects.create(user=self.test_user_1,
|
||||||
|
text="New wombat to adopt", title="🔔 New Wombat alert")
|
||||||
|
not1.mark_read()
|
||||||
|
|
||||||
|
self.assertTrue(not1.read)
|
||||||
|
self.assertFalse(not2.read)
|
||||||
|
self.assertTrue((timezone.now() - timedelta(hours=1)) < not1.read_at < timezone.now())
|
||||||
|
self.assertIsNone(not2.read_at)
|
||||||
|
|
||||||
|
34
src/tests/test_tools_notifications.py
Normal file
34
src/tests/test_tools_notifications.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
from model_bakery import baker
|
||||||
|
|
||||||
|
from fellchensammlung.models import User, TrustLevel, Species, Location, AdoptionNotice, AdoptionNoticeNotification
|
||||||
|
from fellchensammlung.tools.notifications import notify_of_AN_to_be_checked
|
||||||
|
|
||||||
|
|
||||||
|
class TestNotifications(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
cls.test_user0 = User.objects.create_user(username='testuser0',
|
||||||
|
first_name="Admin",
|
||||||
|
last_name="BOFH",
|
||||||
|
password='12345')
|
||||||
|
|
||||||
|
cls.test_user1 = User.objects.create_user(username='testuser1',
|
||||||
|
first_name="Max",
|
||||||
|
last_name="Müller",
|
||||||
|
password='12345')
|
||||||
|
cls.test_user2 = User.objects.create_user(username='testuser2',
|
||||||
|
first_name="Miriam",
|
||||||
|
last_name="Müller",
|
||||||
|
password='12345')
|
||||||
|
cls.test_user0.trust_level = TrustLevel.ADMIN
|
||||||
|
cls.test_user0.save()
|
||||||
|
|
||||||
|
cls.adoption1 = baker.make(AdoptionNotice, name="TestAdoption1", owner=cls.test_user1,)
|
||||||
|
cls.adoption1.set_unchecked() # Could also emit notification
|
||||||
|
|
||||||
|
def test_notify_of_AN_to_be_checked(self):
|
||||||
|
notify_of_AN_to_be_checked(self.adoption1)
|
||||||
|
self.assertTrue(AdoptionNoticeNotification.objects.filter(user=self.test_user0).exists())
|
||||||
|
self.assertTrue(AdoptionNoticeNotification.objects.filter(user=self.test_user1).exists())
|
||||||
|
self.assertFalse(AdoptionNoticeNotification.objects.filter(user=self.test_user2).exists())
|
0
src/tests/test_views/__init__.py
Normal file
0
src/tests/test_views/__init__.py
Normal file
@ -4,7 +4,8 @@ from django.urls import reverse
|
|||||||
|
|
||||||
from model_bakery import baker
|
from model_bakery import baker
|
||||||
|
|
||||||
from fellchensammlung.models import Animal, Species, AdoptionNotice, User, Location, AdoptionNoticeStatus, TrustLevel
|
from fellchensammlung.models import Animal, Species, AdoptionNotice, User, Location, AdoptionNoticeStatus, TrustLevel, \
|
||||||
|
Animal, Subscriptions
|
||||||
from fellchensammlung.views import add_adoption_notice
|
from fellchensammlung.views import add_adoption_notice
|
||||||
|
|
||||||
|
|
||||||
@ -70,7 +71,33 @@ class AnimalAndAdoptionTest(TestCase):
|
|||||||
|
|
||||||
self.assertTrue(response.status_code < 400)
|
self.assertTrue(response.status_code < 400)
|
||||||
self.assertTrue(AdoptionNotice.objects.get(name="TestAdoption4").is_active)
|
self.assertTrue(AdoptionNotice.objects.get(name="TestAdoption4").is_active)
|
||||||
|
an = AdoptionNotice.objects.get(name="TestAdoption4")
|
||||||
|
animals = Animal.objects.filter(adoption_notice=an)
|
||||||
|
self.assertTrue(len(animals) == 2)
|
||||||
|
|
||||||
|
def test_creating_AN_as_user(self):
|
||||||
|
self.client.login(username='testuser1', password='12345')
|
||||||
|
|
||||||
|
form_data = {"name": "TestAdoption5",
|
||||||
|
"species": Species.objects.first().pk,
|
||||||
|
"num_animals": "3",
|
||||||
|
"date_of_birth": "2024-12-04",
|
||||||
|
"sex": "M",
|
||||||
|
"group_only": "on",
|
||||||
|
"searching_since": "2024-11-10",
|
||||||
|
"location_string": "München",
|
||||||
|
"description": "Blaaaa",
|
||||||
|
"further_information": "https://notfellchen.org/",
|
||||||
|
"save-and-add-another-animal": "Speichern"}
|
||||||
|
|
||||||
|
response = self.client.post(reverse('add-adoption'), data=form_data)
|
||||||
|
|
||||||
|
self.assertTrue(response.status_code < 400)
|
||||||
|
self.assertFalse(AdoptionNotice.objects.get(name="TestAdoption5").is_active)
|
||||||
|
an = AdoptionNotice.objects.get(name="TestAdoption5")
|
||||||
|
animals = Animal.objects.filter(adoption_notice=an)
|
||||||
|
self.assertTrue(len(animals) == 3)
|
||||||
|
self.assertTrue(an.sexes == set("M", ))
|
||||||
|
|
||||||
|
|
||||||
class SearchTest(TestCase):
|
class SearchTest(TestCase):
|
||||||
@ -196,3 +223,64 @@ class UpdateQueueTest(TestCase):
|
|||||||
self.assertFalse(self.adoption3.is_active)
|
self.assertFalse(self.adoption3.is_active)
|
||||||
self.assertEqual(self.adoption3.adoptionnoticestatus.major_status, AdoptionNoticeStatus.CLOSED)
|
self.assertEqual(self.adoption3.adoptionnoticestatus.major_status, AdoptionNoticeStatus.CLOSED)
|
||||||
|
|
||||||
|
|
||||||
|
class AdoptionDetailTest(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
test_user0 = User.objects.create_user(username='testuser0',
|
||||||
|
first_name="Admin",
|
||||||
|
last_name="BOFH",
|
||||||
|
password='12345')
|
||||||
|
test_user0.save()
|
||||||
|
|
||||||
|
test_user1 = User.objects.create_user(username='testuser1',
|
||||||
|
first_name="Max",
|
||||||
|
last_name="Müller",
|
||||||
|
password='12345')
|
||||||
|
test_user0.trust_level = TrustLevel.ADMIN
|
||||||
|
test_user0.save()
|
||||||
|
|
||||||
|
adoption1 = baker.make(AdoptionNotice, name="TestAdoption1")
|
||||||
|
adoption2 = baker.make(AdoptionNotice, name="TestAdoption2")
|
||||||
|
adoption3 = baker.make(AdoptionNotice, name="TestAdoption3")
|
||||||
|
|
||||||
|
berlin = Location.get_location_from_string("Berlin")
|
||||||
|
adoption1.location = berlin
|
||||||
|
adoption1.save()
|
||||||
|
|
||||||
|
stuttgart = Location.get_location_from_string("Stuttgart")
|
||||||
|
adoption3.location = stuttgart
|
||||||
|
adoption3.save()
|
||||||
|
|
||||||
|
adoption1.set_active()
|
||||||
|
adoption3.set_active()
|
||||||
|
adoption2.set_unchecked()
|
||||||
|
|
||||||
|
|
||||||
|
def test_subscribe(self):
|
||||||
|
self.client.login(username='testuser0', password='12345')
|
||||||
|
response = self.client.post(
|
||||||
|
reverse('adoption-notice-detail', args=str(AdoptionNotice.objects.get(name="TestAdoption1").pk)),
|
||||||
|
data={"action": "subscribe"})
|
||||||
|
self.assertTrue(Subscriptions.objects.filter(owner__username="testuser0").exists())
|
||||||
|
|
||||||
|
|
||||||
|
def test_unsubscribe(self):
|
||||||
|
# Make sure subscription exists
|
||||||
|
an = AdoptionNotice.objects.get(name="TestAdoption1")
|
||||||
|
user = User.objects.get(username="testuser0")
|
||||||
|
subscription = Subscriptions.objects.get_or_create(owner=user, adoption_notice=an)
|
||||||
|
|
||||||
|
# Unsubscribe
|
||||||
|
self.client.login(username='testuser0', password='12345')
|
||||||
|
response = self.client.post(
|
||||||
|
reverse('adoption-notice-detail', args=str(an.pk)),
|
||||||
|
data={"action": "unsubscribe"})
|
||||||
|
self.assertFalse(Subscriptions.objects.filter(owner__username="testuser0").exists())
|
||||||
|
|
||||||
|
def test_login_required(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse('adoption-notice-detail', args=str(AdoptionNotice.objects.get(name="TestAdoption1").pk)),
|
||||||
|
data={"action": "subscribe"})
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response.url, "/accounts/login/?next=/vermittlung/1/")
|
43
src/tests/test_views/test_basic_views.py
Normal file
43
src/tests/test_views/test_basic_views.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from fellchensammlung.models import User, TrustLevel, AdoptionNotice, Species
|
||||||
|
from model_bakery import baker
|
||||||
|
|
||||||
|
|
||||||
|
class BasicViewTest(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
test_user0 = User.objects.create_user(username='testuser0',
|
||||||
|
first_name="Admin",
|
||||||
|
last_name="BOFH",
|
||||||
|
password='12345')
|
||||||
|
|
||||||
|
test_user1 = User.objects.create_user(username='testuser1',
|
||||||
|
first_name="Max",
|
||||||
|
last_name="Müller",
|
||||||
|
password='12345')
|
||||||
|
test_user0.trust_level = TrustLevel.ADMIN
|
||||||
|
test_user0.save()
|
||||||
|
|
||||||
|
ans = []
|
||||||
|
for i in range(0, 8):
|
||||||
|
ans.append(baker.make(AdoptionNotice, name=f"TestAdoption{i}"))
|
||||||
|
for i in range(0, 4):
|
||||||
|
AdoptionNotice.objects.get(name=f"TestAdoption{i}").set_active()
|
||||||
|
|
||||||
|
def test_index_logged_in(self):
|
||||||
|
self.client.login(username='testuser0', password='12345')
|
||||||
|
|
||||||
|
response = self.client.get(reverse('index'))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
# Check our user is logged in
|
||||||
|
self.assertEqual(str(response.context['user']), 'testuser0')
|
||||||
|
self.assertContains(response, "TestAdoption0")
|
||||||
|
self.assertNotContains(response, "TestAdoption5") # Should not be active, therefore not shown
|
||||||
|
|
||||||
|
def test_index_anonymous(self):
|
||||||
|
response = self.client.get(reverse('index'))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, "TestAdoption1")
|
||||||
|
self.assertNotContains(response, "TestAdoption4") # Should not be active, therefore not shown
|
Loading…
x
Reference in New Issue
Block a user