Skip to content

Commit

Permalink
Merge branch 'main' of github.com:velis74/django-project-base
Browse files Browse the repository at this point in the history
  • Loading branch information
KlemenSpruk committed Oct 10, 2023
2 parents f88a396 + aab2624 commit 3a8f0b4
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 57 deletions.
84 changes: 55 additions & 29 deletions django_project_base/account/rest/project_profiles.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
import re
from io import BytesIO

Expand All @@ -10,6 +11,7 @@
from dynamicforms import fields, serializers
from dynamicforms.action import TableAction, TablePosition
from dynamicforms.viewsets import SingleRecordViewSet
from rest_framework.decorators import action
from rest_framework.exceptions import NotFound, PermissionDenied
from rest_framework.request import Request
from rest_framework.response import Response
Expand Down Expand Up @@ -88,11 +90,11 @@ def get_fields(self):
return res

def translate_relation_fields(self, field_name: str) -> str:
res = super().get_fields()
if field_name in res:
return field_name
else:
ProjectMember = swapper.load_model("django_project_base", "ProjectMember")
if field_name in ProjectMember().project_members_fields_names:
return f"projects__{field_name}"
else:
return field_name

def get_visible_fields(self):
return [(name, field) for (name, field) in self.fields.items() if field.display == fields.DisplayMode.FULL]
Expand All @@ -109,52 +111,54 @@ def filter_queryset_field(self, queryset, field, value):
return queryset.filter(projects__state=value)
return super().filter_queryset_field(queryset, field, value)

def save_club_member_data(self, request: Request, user):
def save_club_member_data(self, request: Request, user, **kwargs):
if user is None:
return
ProjectMember = swapper.load_model("django_project_base", "ProjectMember")

data = {
name: request.data.pop(name, None)
for name in ProjectMember().project_members_fields_names
if name in request.data
}

project = request.selected_project

club_member = None
ProjectMember = swapper.load_model("django_project_base", "ProjectMember")

if project:
club_member = ProjectMember.objects.filter(member=user).filter(project=project).first()

# if club member data cant be retrieved, we can't save anything
if club_member:
for name, value in data.items():
for name, value in kwargs.items():
setattr(club_member, name, value)
club_member.save()

@transaction.atomic
def create(self, request: Request, *args, **kwargs) -> Response:
ProjectMember = swapper.load_model("django_project_base", "ProjectMember")
Profile = swapper.load_model("django_project_base", "Profile")
data = {
name: request.data.pop(name, None)
for name in ProjectMember().project_members_fields_names
if name in request.data
}
# hash password if it exists
if "password" in request.data:
request.data["password"] = make_password(request.data["password"])
response = super().create(request, *args, **kwargs)

ProjectMember = swapper.load_model("django_project_base", "ProjectMember")
Profile = swapper.load_model("django_project_base", "Profile")

user = Profile.objects.get(pk=response.data["id"])
try:
ProjectMember.objects.create(project=request.selected_project, member=user)
except ProjectNotSelectedError as e:
raise PermissionDenied(e.message)

self.save_club_member_data(request, user)
self.save_club_member_data(request, user, **data)
return response

@transaction.atomic
def update(self, request: Request, *args, **kwargs) -> Response:
self.save_club_member_data(request, self.get_object())
ProjectMember = swapper.load_model("django_project_base", "ProjectMember")
data = {
name: request.data.pop(name, None)
for name in ProjectMember().project_members_fields_names
if name in request.data
}
self.save_club_member_data(request, self.get_object(), **data)
# hash password if it exists
if "password" in request.data:
request.data["password"] = make_password(request.data["password"])
Expand Down Expand Up @@ -196,9 +200,38 @@ class ProfileExportViewSet(SingleRecordViewSet):
serializer_class = ProfileExportSerializer

def new_object(self):
return dict(template=None, profile_fields=None, print_filter=True, filter_data=None)
filter_data = self.request.query_params.get("filter_data", None)
if filter_data:
filter_data = json.loads(filter_data)
return dict(template=None, profile_fields=None, print_filter=True, filter_data=filter_data)

def create(self, request, *args, **kwargs):
ser = self.get_serializer(data=request.data) # type: ProfileExportSerializer
ser.is_valid(raise_exception=True)

filter_data = ser.data.get("filter_data", None)
print_filter = ser.data.get("print_filter", False)

file_name = "members"

if filter_data:
active_filters = {name: value for name, value in filter_data.items() if value is not None}
if print_filter and len(active_filters) in [1, 2]:
file_name += "-" + ",".join(["-".join([name, str(value)]) for name, value in active_filters.items()])

data = dict(ser.data)
data["file_name"] = file_name

return Response(data)

@action(
methods=["POST"],
detail=False,
url_path="download",
url_name="profile-export-download",
permission_classes=[],
)
def download(self, request: Request, **kwargs):
profile_serializer = ProjectProfilesSerializer(None, context=self.get_serializer_context(), data=request.data)
visible_profile_fields = {
name: profile_serializer.translate_relation_fields(name)
Expand All @@ -214,18 +247,11 @@ def create(self, request, *args, **kwargs):
profile_fields = ser.data.get("profile_fields", None)
template = ser.data.get("template", None)
filter_data = ser.data.get("filter_data", None)
print_filter = ser.data.get("print_filter", False)
file_name = ser.data.get("file_name", "members")

profile_items_q = get_project_members(self.request)

file_name = "members"

if filter_data:
active_filters = {name: value for name, value in filter_data.items() if value is not None}
print(len(active_filters), print_filter)
if print_filter and len(active_filters) in [1, 2]:
file_name += "-" + ",".join(["-".join([name, str(value)]) for name, value in active_filters.items()])
pass
for field_name, value in filter_data.items():
profile_items_q = filter_project_members_fields(profile_items_q, field_name, value)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import re
from abc import ABC, abstractmethod
from html import unescape
from typing import Union

import swapper
from django.urls import reverse
from django.utils.html import strip_tags

from django_project_base.constants import SEND_NOTIFICATION_SMS
Expand Down Expand Up @@ -58,17 +60,20 @@ def get_message(self, notification: DjangoProjectBaseNotification) -> Union[dict

def get_send_notification_sms_text(self, notification: DjangoProjectBaseNotification, host_url: str) -> str:
if notification.send_notification_sms:
template: str = swapper.load_model("django_project_base", "ProjectSettings").objects.get(
name=SEND_NOTIFICATION_SMS, project__slug=notification.project_slug).python_value
template: str = (
swapper.load_model("django_project_base", "ProjectSettings")
.objects.get(name=SEND_NOTIFICATION_SMS, project__slug=notification.project_slug)
.python_value
)
return template.replace(
"__LINK__",
f"{host_url}notification/{str(notification.pk)}/info"
f"{'/' if getattr(self.settings, 'APPEND_SLASH', False) else ''}")
f"{host_url.rstrip('/')}"
f"{reverse('notification-notification-login', kwargs=dict(pk=str(notification.pk)))}",
)
return ""

@abstractmethod
def _get_sms_message(self, notification: DjangoProjectBaseNotification) -> Union[dict, str]:

if notification.send_notification_sms:
return notification.send_notification_sms_text

Expand All @@ -77,9 +82,14 @@ def _get_sms_message(self, notification: DjangoProjectBaseNotification) -> Union
if notification.message.subject:
message += "\n\n"

message += notification.message.body

message += (
notification.message.body.replace("&nbsp;</p>", "\n")
.replace("</p>", "\n")
.replace("\n&nbsp;", "\n")
.replace("&nbsp;", "\n")
)
text_only = re.sub("[ \t]+", " ", strip_tags(message))
# Strip single spaces in the beginning of each line
message = text_only.replace("\n ", "\n").replace("\n", "\r\n").strip()
return message
message = text_only.replace("\n ", "\n")
message = message.replace("\n", "\r\n")
return unescape(message)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List, Union
from typing import List

from django.conf import settings

Expand All @@ -16,9 +16,6 @@ class SmsChannel(Channel):

provider_setting_name = "SMS_PROVIDER"

def get_message(self, notification: DjangoProjectBaseNotification) -> Union[dict, str]:
return self._get_sms_message(notification)

def get_recipients(self, notification: DjangoProjectBaseNotification, unique_identifier=""):
return list(set(super().get_recipients(notification, unique_identifier="phone_number")))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def forwards_func(apps, schema_editor):
project=project,
name=SEND_NOTIFICATION_SMS,
defaults=dict(
description="Text for notification detail view. __LINK__ is url placeholder.",
description=gettext("Text for notification detail view. __LINK__ is url placeholder."),
value=gettext("You received a notification. Clik link to view it: __LINK__"),
value_type="char",
),
Expand Down
4 changes: 2 additions & 2 deletions django_project_base/notifications/rest/notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@

import pytz
import swapper
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.cache import cache
from django.db.models import ForeignKey, QuerySet
from django.http import Http404, HttpResponse
from django.shortcuts import render
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from drf_spectacular.utils import extend_schema
from dynamicforms import fields
Expand Down Expand Up @@ -350,7 +350,7 @@ def notification_login(self, request, pk=None) -> HttpResponse:
"notification_login.html",
dict(
identifier=pk,
url=f"/notification/info-view{'/' if getattr(settings, 'APPEND_SLASH', False) else ''}",
url=reverse("notification-notification-view"),
title=swapper.load_model("django_project_base", "Project")
.objects.get(slug=DjangoProjectBaseNotification.objects.get(pk=pk).project_slug)
.name,
Expand Down
6 changes: 3 additions & 3 deletions vue/components/notifications-editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
APIConsumer,
ComponentDisplay,
ConsumerLogicApi,
FormConsumerApiOneShot, gettext,
FormConsumerOneShotApi, gettext,
useActionHandler,
} from '@velis/dynamicforms';
import { onMounted, onUnmounted, ref } from 'vue';
Expand Down Expand Up @@ -50,12 +50,12 @@ const notificationLogic = ref(new ConsumerLogicApi(
notificationLogic.value.getFullDefinition();
const actionViewLicense = async (): Promise<boolean> => {
await FormConsumerApiOneShot({ url: licenseConsumerUrl, trailingSlash: licenseConsumerUrlTrailingSlash, pk: 'new' });
await FormConsumerOneShotApi({ url: licenseConsumerUrl, trailingSlash: licenseConsumerUrlTrailingSlash, pk: 'new' });
return true;
};
const actionAddNotification = async (): Promise<boolean> => {
await FormConsumerApiOneShot({
await FormConsumerOneShotApi({
url: consumerUrl,
trailingSlash: consumerTrailingSlash,
pk: 'new',
Expand Down
4 changes: 2 additions & 2 deletions vue/components/user-session/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
ConsumerLogicApi, dfModal as dfModalApi,
dfModal, DialogSize,
DisplayMode,
FilteredActions, FormConsumerApiOneShot,
FilteredActions, FormConsumerOneShotApi,
FormPayload,
gettext,
} from '@velis/dynamicforms';
Expand Down Expand Up @@ -221,7 +221,7 @@ export default function useLogin() {
socialAuth.value = formDef.payload.social_auth_providers;
}

const openRegistration = async () => FormConsumerApiOneShot(
const openRegistration = async () => FormConsumerOneShotApi(
{ url: '/account/profile/register', trailingSlash: false },
);

Expand Down
6 changes: 3 additions & 3 deletions vue/components/user-session/project-list.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
Action,
apiClient,
dfModal,
FormConsumerApiOneShot,
FormConsumerOneShotApi,
FormPayload,
} from '@velis/dynamicforms';
import slugify from 'slugify';
Expand Down Expand Up @@ -46,7 +46,7 @@ async function loadData() {
}
async function addNewProject() {
// const addProjectModal = await FormConsumerApiOneShot({ url: '/project', trailingSlash: false, pk: 'new' });
// const addProjectModal = await FormConsumerOneShotApi({ url: '/project', trailingSlash: false, pk: 'new' });
let slugChanged = false;
let ignoreSlugChange = false;
const valueChangedHandler = (action: Action, payload: FormPayload, context: any) => {
Expand All @@ -60,7 +60,7 @@ async function addNewProject() {
}
return false;
};
const addProjectModal = await FormConsumerApiOneShot(
const addProjectModal = await FormConsumerOneShotApi(
{
url: '/project',
trailingSlash: false,
Expand Down
8 changes: 4 additions & 4 deletions vue/components/user-session/user-profile.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { Action, dfModal, DialogSize, FilteredActions, FormConsumerApiOneShot, gettext } from '@velis/dynamicforms';
import { Action, dfModal, DialogSize, FilteredActions, FormConsumerOneShotApi, gettext } from '@velis/dynamicforms';
import axios from 'axios';
import _ from 'lodash';
import { computed, h, onMounted, watch } from 'vue';
Expand Down Expand Up @@ -30,7 +30,7 @@ let socialConnectionsModalPromise = null as any;
const showProjectList = computed(() => (props.projectListComponent && userSession.loggedIn && display.smAndDown.value));
async function changePassword() {
await FormConsumerApiOneShot({ url: '/account/change-password/', trailingSlash: true, pk: 'new' });
await FormConsumerOneShotApi({ url: '/account/change-password/', trailingSlash: true, pk: 'new' });
}
async function checkResetPassword() {
Expand All @@ -52,7 +52,7 @@ watch(() => userSession.impersonated, () => window.location.reload());
onMounted(() => loadData());
async function showImpersonateLogin() {
await FormConsumerApiOneShot({ url: '/account/impersonate', trailingSlash: false });
await FormConsumerOneShotApi({ url: '/account/impersonate', trailingSlash: false });
await userSession.checkLogin(false);
}
Expand All @@ -62,7 +62,7 @@ async function stopImpersonation() {
}
async function userProfile() {
await FormConsumerApiOneShot({ url: '/account/profile/current', trailingSlash: false });
await FormConsumerOneShotApi({ url: '/account/profile/current', trailingSlash: false });
await userSession.checkLogin(false);
}
Expand Down

0 comments on commit 3a8f0b4

Please sign in to comment.