Use project contact form handler and superuser-only snippet access
This commit is contained in:
@@ -4,4 +4,3 @@ Ocyan loads contact form handlers via module labels like `contact_form.views`.
|
|||||||
By shipping this package in the project repository we can extend behavior
|
By shipping this package in the project repository we can extend behavior
|
||||||
without forking the upstream plugin.
|
without forking the upstream plugin.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ from django.utils.translation import gettext as _
|
|||||||
from django.views.decorators.http import require_http_methods
|
from django.views.decorators.http import require_http_methods
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
from oscar.core.utils import redirect_to_referrer
|
|
||||||
from wagtail.models import Locale, Site
|
from wagtail.models import Locale, Site
|
||||||
|
|
||||||
|
from oscar.core.utils import redirect_to_referrer
|
||||||
|
|
||||||
from ocyan.core.fender import config
|
from ocyan.core.fender import config
|
||||||
from ocyan.plugin.contact_form.forms import ContactForm
|
from ocyan.plugin.contact_form.forms import ContactForm
|
||||||
from ocyan.plugin.contact_form.utils import get_from_email, get_to_email
|
from ocyan.plugin.contact_form.utils import get_from_email, get_to_email
|
||||||
@@ -30,6 +31,7 @@ def _client_ip(request) -> str | None:
|
|||||||
return forwarded_for.split(",")[0].strip() or None
|
return forwarded_for.split(",")[0].strip() or None
|
||||||
return request.META.get("REMOTE_ADDR")
|
return request.META.get("REMOTE_ADDR")
|
||||||
|
|
||||||
|
|
||||||
def _active_locale(request) -> Locale:
|
def _active_locale(request) -> Locale:
|
||||||
language_code = (getattr(request, "LANGUAGE_CODE", "") or "").split("-")[0]
|
language_code = (getattr(request, "LANGUAGE_CODE", "") or "").split("-")[0]
|
||||||
if language_code:
|
if language_code:
|
||||||
@@ -51,7 +53,11 @@ def post_contact_form(request):
|
|||||||
message_obj = ContactMessage.objects.create(
|
message_obj = ContactMessage.objects.create(
|
||||||
site=site,
|
site=site,
|
||||||
locale=locale,
|
locale=locale,
|
||||||
user=request.user if getattr(request.user, "is_authenticated", False) else None,
|
user=(
|
||||||
|
request.user
|
||||||
|
if getattr(request.user, "is_authenticated", False)
|
||||||
|
else None
|
||||||
|
),
|
||||||
ip_address=_client_ip(request),
|
ip_address=_client_ip(request),
|
||||||
path=request.path or "",
|
path=request.path or "",
|
||||||
name=str(cleaned.get("name", "")),
|
name=str(cleaned.get("name", "")),
|
||||||
@@ -59,7 +65,11 @@ def post_contact_form(request):
|
|||||||
phone_number=str(cleaned.get("phonenumber") or ""),
|
phone_number=str(cleaned.get("phonenumber") or ""),
|
||||||
message=str(cleaned.get("message", "")),
|
message=str(cleaned.get("message", "")),
|
||||||
)
|
)
|
||||||
logger.info("Saved ContactMessage id=%s email=%s", message_obj.id, message_obj.email)
|
logger.info(
|
||||||
|
"Saved ContactMessage id=%s email=%s",
|
||||||
|
message_obj.id,
|
||||||
|
message_obj.email,
|
||||||
|
)
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"website_url": request.build_absolute_uri(),
|
"website_url": request.build_absolute_uri(),
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
def grant_contactmessage_permissions(apps, schema_editor):
|
|
||||||
Group = apps.get_model("auth", "Group")
|
|
||||||
Permission = apps.get_model("auth", "Permission")
|
|
||||||
ContentType = apps.get_model("contenttypes", "ContentType")
|
|
||||||
|
|
||||||
try:
|
|
||||||
group = Group.objects.get(name="Editors")
|
|
||||||
except Group.DoesNotExist:
|
|
||||||
return
|
|
||||||
|
|
||||||
content_type = ContentType.objects.get(app_label="mandelstudio", model="contactmessage")
|
|
||||||
perms = Permission.objects.filter(
|
|
||||||
content_type=content_type,
|
|
||||||
codename__in=[
|
|
||||||
"add_contactmessage",
|
|
||||||
"change_contactmessage",
|
|
||||||
"delete_contactmessage",
|
|
||||||
"view_contactmessage",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
group.permissions.add(*perms)
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("mandelstudio", "0004_contact_messages"),
|
|
||||||
("contenttypes", "0002_remove_content_type_name"),
|
|
||||||
("auth", "0012_alter_user_first_name_max_length"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(
|
|
||||||
grant_contactmessage_permissions,
|
|
||||||
reverse_code=migrations.RunPython.noop,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
def grant_contactmessage_permissions_to_staff(apps, schema_editor):
|
|
||||||
Permission = apps.get_model("auth", "Permission")
|
|
||||||
ContentType = apps.get_model("contenttypes", "ContentType")
|
|
||||||
|
|
||||||
# Default Django user model in this project.
|
|
||||||
User = apps.get_model("auth", "User")
|
|
||||||
|
|
||||||
content_type = ContentType.objects.get(app_label="mandelstudio", model="contactmessage")
|
|
||||||
perms = list(
|
|
||||||
Permission.objects.filter(
|
|
||||||
content_type=content_type,
|
|
||||||
codename__in=[
|
|
||||||
"add_contactmessage",
|
|
||||||
"change_contactmessage",
|
|
||||||
"delete_contactmessage",
|
|
||||||
"view_contactmessage",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if not perms:
|
|
||||||
return
|
|
||||||
|
|
||||||
for user in User.objects.filter(is_staff=True, is_active=True).iterator():
|
|
||||||
user.user_permissions.add(*perms)
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("mandelstudio", "0005_grant_contactmessage_permissions"),
|
|
||||||
("contenttypes", "0002_remove_content_type_name"),
|
|
||||||
("auth", "0012_alter_user_first_name_max_length"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(
|
|
||||||
grant_contactmessage_permissions_to_staff,
|
|
||||||
reverse_code=migrations.RunPython.noop,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
def remove_contactmessage_permissions_from_editors(apps, schema_editor):
|
|
||||||
Group = apps.get_model("auth", "Group")
|
|
||||||
Permission = apps.get_model("auth", "Permission")
|
|
||||||
ContentType = apps.get_model("contenttypes", "ContentType")
|
|
||||||
|
|
||||||
try:
|
|
||||||
group = Group.objects.get(name="Editors")
|
|
||||||
except Group.DoesNotExist:
|
|
||||||
return
|
|
||||||
|
|
||||||
content_type = ContentType.objects.get(app_label="mandelstudio", model="contactmessage")
|
|
||||||
perms = Permission.objects.filter(
|
|
||||||
content_type=content_type,
|
|
||||||
codename__in=[
|
|
||||||
"add_contactmessage",
|
|
||||||
"change_contactmessage",
|
|
||||||
"delete_contactmessage",
|
|
||||||
"view_contactmessage",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
group.permissions.remove(*perms)
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("mandelstudio", "0006_grant_contactmessage_permissions_to_staff"),
|
|
||||||
("contenttypes", "0002_remove_content_type_name"),
|
|
||||||
("auth", "0012_alter_user_first_name_max_length"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(
|
|
||||||
remove_contactmessage_permissions_from_editors,
|
|
||||||
reverse_code=migrations.RunPython.noop,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
37
mandelstudio/tests/test_contact_form_routing.py
Normal file
37
mandelstudio/tests/test_contact_form_routing.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.test import SimpleTestCase, TestCase
|
||||||
|
from django.urls import resolve
|
||||||
|
|
||||||
|
from ocyan.plugin.contact_form.entrypoint import SHOP_BASE_URL
|
||||||
|
|
||||||
|
from contact_form.views import post_contact_form
|
||||||
|
from mandelstudio.models import ContactMessage
|
||||||
|
from mandelstudio.wagtail_hooks import SuperuserOnlyPermissionPolicy
|
||||||
|
|
||||||
|
|
||||||
|
class ContactFormRoutingTests(SimpleTestCase):
|
||||||
|
def test_shop_contact_form_uses_project_handler(self):
|
||||||
|
match = resolve(f"/{SHOP_BASE_URL}/contact-form/")
|
||||||
|
|
||||||
|
self.assertIs(match.func, post_contact_form)
|
||||||
|
|
||||||
|
|
||||||
|
class ContactMessagePermissionPolicyTests(TestCase):
|
||||||
|
def test_only_superusers_have_contact_message_permissions(self):
|
||||||
|
user_model = get_user_model()
|
||||||
|
superuser = user_model.objects.create_superuser(
|
||||||
|
"contact-superuser",
|
||||||
|
"superuser@example.com",
|
||||||
|
"password",
|
||||||
|
)
|
||||||
|
staff_user = user_model.objects.create_user(
|
||||||
|
"contact-staff",
|
||||||
|
"staff@example.com",
|
||||||
|
"password",
|
||||||
|
is_staff=True,
|
||||||
|
)
|
||||||
|
policy = SuperuserOnlyPermissionPolicy(ContactMessage)
|
||||||
|
|
||||||
|
self.assertTrue(policy.user_has_permission(superuser, "view"))
|
||||||
|
self.assertFalse(policy.user_has_permission(staff_user, "view"))
|
||||||
|
self.assertFalse(policy.user_has_any_permission(staff_user, {"view", "change"}))
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
|
from django.conf.urls.i18n import i18n_patterns
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from django.views.decorators.cache import cache_page
|
from django.views.decorators.cache import cache_page
|
||||||
|
|
||||||
|
from ocyan.core.fender import config
|
||||||
from ocyan.main.urls import urlpatterns as ocyan_urlpatterns
|
from ocyan.main.urls import urlpatterns as ocyan_urlpatterns
|
||||||
|
from ocyan.plugin.contact_form.entrypoint import SHOP_BASE_URL
|
||||||
from ocyan.plugin.wagtail_oscar_integration.constants import CACHE_DURATION
|
from ocyan.plugin.wagtail_oscar_integration.constants import CACHE_DURATION
|
||||||
|
|
||||||
|
from contact_form.views import post_contact_form
|
||||||
|
|
||||||
from .i18n_views import set_language_normalized
|
from .i18n_views import set_language_normalized
|
||||||
from .sitemaps import robots_txt, sitemap_index, sitemap_section
|
from .sitemaps import robots_txt, sitemap_index, sitemap_section
|
||||||
|
|
||||||
@@ -22,4 +27,20 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
contact_form_urlpatterns = [
|
||||||
|
path(
|
||||||
|
f"{SHOP_BASE_URL}/contact-form/",
|
||||||
|
post_contact_form,
|
||||||
|
name="project-contact-form-handler",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
if config.i18n_enabled:
|
||||||
|
urlpatterns += i18n_patterns(
|
||||||
|
*contact_form_urlpatterns,
|
||||||
|
prefix_default_language=False,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
urlpatterns += contact_form_urlpatterns
|
||||||
|
|
||||||
urlpatterns += ocyan_urlpatterns
|
urlpatterns += ocyan_urlpatterns
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
from wagtail.permissions import ModelPermissionPolicy
|
||||||
from wagtail.snippets.models import register_snippet
|
from wagtail.snippets.models import register_snippet
|
||||||
from wagtail.snippets.views.snippets import SnippetViewSet
|
from wagtail.snippets.views.snippets import SnippetViewSet
|
||||||
|
|
||||||
@@ -5,6 +8,40 @@ from mandelblog_content_guard.hooks import * # noqa: F401,F403
|
|||||||
from mandelstudio.models import ContactMessage
|
from mandelstudio.models import ContactMessage
|
||||||
|
|
||||||
|
|
||||||
|
class SuperuserOnlyPermissionPolicy(ModelPermissionPolicy):
|
||||||
|
def user_has_permission(self, user, action):
|
||||||
|
return user.is_active and user.is_superuser
|
||||||
|
|
||||||
|
def user_has_any_permission(self, user, actions):
|
||||||
|
return user.is_active and user.is_superuser
|
||||||
|
|
||||||
|
def user_has_permission_for_instance(self, user, action, instance):
|
||||||
|
return self.user_has_permission(user, action)
|
||||||
|
|
||||||
|
def user_has_any_permission_for_instance(self, user, actions, instance):
|
||||||
|
return self.user_has_any_permission(user, actions)
|
||||||
|
|
||||||
|
def instances_user_has_any_permission_for(self, user, actions):
|
||||||
|
if self.user_has_any_permission(user, actions):
|
||||||
|
return self.model._default_manager.all()
|
||||||
|
return self.model._default_manager.none()
|
||||||
|
|
||||||
|
def instances_user_has_permission_for(self, user, action):
|
||||||
|
return self.instances_user_has_any_permission_for(user, [action])
|
||||||
|
|
||||||
|
def users_with_any_permission(self, actions):
|
||||||
|
return get_user_model().objects.filter(is_active=True, is_superuser=True)
|
||||||
|
|
||||||
|
def users_with_permission(self, action):
|
||||||
|
return self.users_with_any_permission([action])
|
||||||
|
|
||||||
|
def users_with_any_permission_for_instance(self, actions, instance):
|
||||||
|
return self.users_with_any_permission(actions)
|
||||||
|
|
||||||
|
def users_with_permission_for_instance(self, action, instance):
|
||||||
|
return self.users_with_any_permission_for_instance([action], instance)
|
||||||
|
|
||||||
|
|
||||||
@register_snippet
|
@register_snippet
|
||||||
class ContactMessageViewSet(SnippetViewSet):
|
class ContactMessageViewSet(SnippetViewSet):
|
||||||
model = ContactMessage
|
model = ContactMessage
|
||||||
@@ -19,3 +56,7 @@ class ContactMessageViewSet(SnippetViewSet):
|
|||||||
list_filter = ("locale", "site")
|
list_filter = ("locale", "site")
|
||||||
search_fields = ("name", "email", "message", "phone_number")
|
search_fields = ("name", "email", "message", "phone_number")
|
||||||
ordering = ("-created_at",)
|
ordering = ("-created_at",)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def permission_policy(self):
|
||||||
|
return SuperuserOnlyPermissionPolicy(self.model)
|
||||||
|
|||||||
Reference in New Issue
Block a user