Compare commits
4 Commits
769f18dac8
...
40ddab4627
| Author | SHA1 | Date | |
|---|---|---|---|
| 40ddab4627 | |||
| e67ef526f4 | |||
| 666ec88165 | |||
| 18181113a9 |
@@ -45,9 +45,11 @@ INSTALLED_APPS = [
|
|||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
"constance",
|
"constance",
|
||||||
"tailwind",
|
"tailwind",
|
||||||
|
"django_filters",
|
||||||
"rules.apps.AutodiscoverRulesConfig",
|
"rules.apps.AutodiscoverRulesConfig",
|
||||||
"theme.apps.ThemeConfig", # Tailwind theme app
|
"theme.apps.ThemeConfig", # Tailwind theme app
|
||||||
"members.apps.MembersConfig",
|
"members.apps.MembersConfig",
|
||||||
|
"backend.apps.BackendConfig",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
@@ -129,6 +131,11 @@ STATICFILES_DIRS = [BASE_DIR / "static"]
|
|||||||
MEDIA_URL = "media/"
|
MEDIA_URL = "media/"
|
||||||
MEDIA_ROOT = BASE_DIR / "media"
|
MEDIA_ROOT = BASE_DIR / "media"
|
||||||
|
|
||||||
|
AUTHENTICATION_BACKENDS = [
|
||||||
|
"rules.permissions.ObjectPermissionBackend",
|
||||||
|
"django.contrib.auth.backends.ModelBackend",
|
||||||
|
]
|
||||||
|
|
||||||
CONSTANCE_BACKEND = "constance.backends.database.DatabaseBackend"
|
CONSTANCE_BACKEND = "constance.backends.database.DatabaseBackend"
|
||||||
CONSTANCE_CONFIG = {
|
CONSTANCE_CONFIG = {
|
||||||
"TF_CLUB_NAME": (config("TF_CLUB_NAME", default="TeamForge", cast=str), "Club Name", str),
|
"TF_CLUB_NAME": (config("TF_CLUB_NAME", default="TeamForge", cast=str), "Club Name", str),
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ Including another URLconf
|
|||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path
|
from django.urls import include, path
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
path('backend/', include('backend.urls')),
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
]
|
]
|
||||||
|
|||||||
0
backend/__init__.py
Normal file
0
backend/__init__.py
Normal file
5
backend/apps.py
Normal file
5
backend/apps.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class BackendConfig(AppConfig):
|
||||||
|
name = "backend"
|
||||||
0
backend/members/__init__.py
Normal file
0
backend/members/__init__.py
Normal file
12
backend/members/urls.py
Normal file
12
backend/members/urls.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from django.urls import include, path
|
||||||
|
|
||||||
|
from .views import MemberAddView, MemberDeleteView, MemberEditView, MemberListView, MemberLoadView
|
||||||
|
|
||||||
|
app_name = "members"
|
||||||
|
urlpatterns = [
|
||||||
|
path("", MemberListView.as_view(), name="list"),
|
||||||
|
# path("add/", MemberAddView.as_view(), name="add"),
|
||||||
|
# path("<int:pk>/edit/", MemberEditView.as_view(), name="edit"),
|
||||||
|
# path("<int:pk>/delete/", MemberDeleteView.as_view(), name="delete"),
|
||||||
|
# path("load/", MemberLoadView.as_view(), name="load"),
|
||||||
|
]
|
||||||
34
backend/members/views.py
Normal file
34
backend/members/views.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.http import HttpResponse, HttpResponseRedirect
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django_filters.views import FilterView
|
||||||
|
from rules.contrib.views import PermissionRequiredMixin
|
||||||
|
|
||||||
|
from members.filters import MemberFilter
|
||||||
|
from members.models import Member
|
||||||
|
|
||||||
|
|
||||||
|
class MemberListView(PermissionRequiredMixin, FilterView):
|
||||||
|
filterset_class = MemberFilter
|
||||||
|
paginate_by = 50
|
||||||
|
permission_denied_message = _("You do not have permission to view this page.")
|
||||||
|
permission_required = "members.view_member"
|
||||||
|
|
||||||
|
def handle_no_permission(self) -> HttpResponseRedirect:
|
||||||
|
messages.error(self.request, self.get_permission_denied_message())
|
||||||
|
return HttpResponseRedirect(reverse_lazy("backend:index"))
|
||||||
|
|
||||||
|
|
||||||
|
class MemberAddView: ...
|
||||||
|
|
||||||
|
|
||||||
|
class MemberEditView: ...
|
||||||
|
|
||||||
|
|
||||||
|
class MemberDeleteView: ...
|
||||||
|
|
||||||
|
|
||||||
|
class MemberLoadView: ...
|
||||||
9
backend/urls.py
Normal file
9
backend/urls.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from django.urls import include, path
|
||||||
|
|
||||||
|
from .views import index
|
||||||
|
|
||||||
|
app_name = "backend"
|
||||||
|
urlpatterns = [
|
||||||
|
path("", index, name="index"),
|
||||||
|
path("members/", include("backend.members.urls")),
|
||||||
|
]
|
||||||
6
backend/views.py
Normal file
6
backend/views.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
|
def index(request):
|
||||||
|
return render(request, "backend/index.html")
|
||||||
@@ -3,3 +3,7 @@ from django.apps import AppConfig
|
|||||||
|
|
||||||
class MembersConfig(AppConfig):
|
class MembersConfig(AppConfig):
|
||||||
name = "members"
|
name = "members"
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
# noinspection PyUnusedImports
|
||||||
|
import members.signals
|
||||||
|
|||||||
16
members/filters.py
Normal file
16
members/filters.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import django_filters
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from .models import Member
|
||||||
|
|
||||||
|
|
||||||
|
class MemberFilter(django_filters.FilterSet):
|
||||||
|
user__first_name = django_filters.CharFilter(field_name="user__first_name", label=_("First name"))
|
||||||
|
user__last_name = django_filters.CharFilter(field_name="user__last_name", label=_("Last name"))
|
||||||
|
license = django_filters.CharFilter(label=_("License"), lookup_expr="icontains")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Member
|
||||||
|
fields = ["user__first_name", "user__last_name", "license"]
|
||||||
|
|
||||||
|
|
||||||
19
members/signals.py
Normal file
19
members/signals.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import secrets
|
||||||
|
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.db.models.signals import post_save
|
||||||
|
from django.dispatch import receiver
|
||||||
|
|
||||||
|
from .models import Member
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=User)
|
||||||
|
def create_member_profile(instance, *args, **kwargs) -> None:
|
||||||
|
"""Creates a Member profile for a newly created user."""
|
||||||
|
member, created = Member.objects.get_or_create(user=instance)
|
||||||
|
|
||||||
|
if not member.access_token:
|
||||||
|
member.access_token = secrets.token_urlsafe(64)
|
||||||
|
member.save(update_fields=["access_token"])
|
||||||
@@ -1,3 +1,30 @@
|
|||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.contrib.auth.models import Permission
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
# Create your tests here.
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
class MembersTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.user_a = User.objects.create(username="user_a", first_name="User", last_name="A", email="user_a@test.com")
|
||||||
|
|
||||||
|
def testMemberName(self):
|
||||||
|
self.assertEqual(str(self.user_a.member), "User A")
|
||||||
|
|
||||||
|
def testMemberCreation(self):
|
||||||
|
self.assertTrue(hasattr(self.user_a, "member"))
|
||||||
|
|
||||||
|
def testMemberManager(self):
|
||||||
|
self.assertFalse(self.user_a.has_perm("members.member_manager"))
|
||||||
|
self.assertFalse(self.user_a.has_perm("members.add_member"))
|
||||||
|
self.assertFalse(self.user_a.is_superuser)
|
||||||
|
|
||||||
|
member_manager_permission = Permission.objects.get(codename="member_manager")
|
||||||
|
|
||||||
|
self.user_a.user_permissions.add(member_manager_permission)
|
||||||
|
self.user_a = User.objects.get(pk=self.user_a.pk)
|
||||||
|
|
||||||
|
self.assertTrue(self.user_a.has_perm("members.member_manager"))
|
||||||
|
self.assertTrue(self.user_a.has_perm("members.add_member"))
|
||||||
|
self.assertFalse(self.user_a.is_superuser)
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
from django.shortcuts import render
|
|
||||||
|
|
||||||
# Create your views here.
|
|
||||||
@@ -8,6 +8,7 @@ dependencies = [
|
|||||||
"django>=6.0",
|
"django>=6.0",
|
||||||
"django-constance>=4.3.4",
|
"django-constance>=4.3.4",
|
||||||
"django-extensions>=4.1",
|
"django-extensions>=4.1",
|
||||||
|
"django-filter>=25.2",
|
||||||
"django-phonenumber-field[phonenumbers]>=8.4.0",
|
"django-phonenumber-field[phonenumbers]>=8.4.0",
|
||||||
"django-tailwind[cookiecutter,honcho]>=4.4.2",
|
"django-tailwind[cookiecutter,honcho]>=4.4.2",
|
||||||
"psycopg2-binary>=2.9.11",
|
"psycopg2-binary>=2.9.11",
|
||||||
|
|||||||
1
templates/backend/index.html
Normal file
1
templates/backend/index.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
TEST FILE
|
||||||
1
templates/members/member_filter.html
Normal file
1
templates/members/member_filter.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
TEST FILE 2
|
||||||
14
uv.lock
generated
14
uv.lock
generated
@@ -243,6 +243,18 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/64/96/d967ca440d6a8e3861120f51985d8e5aec79b9a8bdda16041206adfe7adc/django_extensions-4.1-py3-none-any.whl", hash = "sha256:0699a7af28f2523bf8db309a80278519362cd4b6e1fd0a8cd4bf063e1e023336", size = 232980, upload-time = "2025-04-11T01:15:37.701Z" },
|
{ url = "https://files.pythonhosted.org/packages/64/96/d967ca440d6a8e3861120f51985d8e5aec79b9a8bdda16041206adfe7adc/django_extensions-4.1-py3-none-any.whl", hash = "sha256:0699a7af28f2523bf8db309a80278519362cd4b6e1fd0a8cd4bf063e1e023336", size = 232980, upload-time = "2025-04-11T01:15:37.701Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-filter"
|
||||||
|
version = "25.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "django" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/2c/e4/465d2699cd388c0005fb8d6ae6709f239917c6d8790ac35719676fffdcf3/django_filter-25.2.tar.gz", hash = "sha256:760e984a931f4468d096f5541787efb8998c61217b73006163bf2f9523fe8f23", size = 143818, upload-time = "2025-10-05T09:51:31.521Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c1/40/6a02495c5658beb1f31eb09952d8aa12ef3c2a66342331ce3a35f7132439/django_filter-25.2-py3-none-any.whl", hash = "sha256:9c0f8609057309bba611062fe1b720b4a873652541192d232dd28970383633e3", size = 94145, upload-time = "2025-10-05T09:51:29.728Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-phonenumber-field"
|
name = "django-phonenumber-field"
|
||||||
version = "8.4.0"
|
version = "8.4.0"
|
||||||
@@ -603,6 +615,7 @@ dependencies = [
|
|||||||
{ name = "django" },
|
{ name = "django" },
|
||||||
{ name = "django-constance" },
|
{ name = "django-constance" },
|
||||||
{ name = "django-extensions" },
|
{ name = "django-extensions" },
|
||||||
|
{ name = "django-filter" },
|
||||||
{ name = "django-phonenumber-field", extra = ["phonenumbers"] },
|
{ name = "django-phonenumber-field", extra = ["phonenumbers"] },
|
||||||
{ name = "django-tailwind", extra = ["cookiecutter", "honcho"] },
|
{ name = "django-tailwind", extra = ["cookiecutter", "honcho"] },
|
||||||
{ name = "psycopg2-binary" },
|
{ name = "psycopg2-binary" },
|
||||||
@@ -622,6 +635,7 @@ requires-dist = [
|
|||||||
{ name = "django", specifier = ">=6.0" },
|
{ name = "django", specifier = ">=6.0" },
|
||||||
{ name = "django-constance", specifier = ">=4.3.4" },
|
{ name = "django-constance", specifier = ">=4.3.4" },
|
||||||
{ name = "django-extensions", specifier = ">=4.1" },
|
{ name = "django-extensions", specifier = ">=4.1" },
|
||||||
|
{ name = "django-filter", specifier = ">=25.2" },
|
||||||
{ name = "django-phonenumber-field", extras = ["phonenumbers"], specifier = ">=8.4.0" },
|
{ name = "django-phonenumber-field", extras = ["phonenumbers"], specifier = ">=8.4.0" },
|
||||||
{ name = "django-tailwind", extras = ["cookiecutter", "honcho"], specifier = ">=4.4.2" },
|
{ name = "django-tailwind", extras = ["cookiecutter", "honcho"], specifier = ">=4.4.2" },
|
||||||
{ name = "psycopg2-binary", specifier = ">=2.9.11" },
|
{ name = "psycopg2-binary", specifier = ">=2.9.11" },
|
||||||
|
|||||||
Reference in New Issue
Block a user