space management page progress

This commit is contained in:
vabene1111
2022-06-02 16:20:52 +02:00
parent 2afab2aec8
commit 07f78bb7b8
4 changed files with 93 additions and 77 deletions

View File

@ -1,9 +1,13 @@
from datetime import timedelta from datetime import timedelta, datetime
from decimal import Decimal from decimal import Decimal
from gettext import gettext as _ from gettext import gettext as _
from html import escape
from smtplib import SMTPException
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group
from django.core.mail import send_mail
from django.db.models import Avg, Q, QuerySet, Sum from django.db.models import Avg, Q, QuerySet, Sum
from django.http import BadHeaderError
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from drf_writable_nested import UniqueFieldsMixin, WritableNestedModelSerializer from drf_writable_nested import UniqueFieldsMixin, WritableNestedModelSerializer
@ -1032,13 +1036,35 @@ class InviteLinkSerializer(WritableNestedModelSerializer):
def create(self, validated_data): def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].space validated_data['space'] = self.context['request'].space
return super().create(validated_data) obj = super().create(validated_data)
if obj.email:
try:
if InviteLink.objects.filter(space=self.context['request'].space, created_at__gte=datetime.now() - timedelta(hours=4)).count() < 20:
message = _('Hello') + '!\n\n' + _('You have been invited by ') + escape(self.context['request'].user.username)
message += _(' to join their Tandoor Recipes space ') + escape(self.context['request'].space.name) + '.\n\n'
message += _('Click the following link to activate your account: ') + self.context['request'].build_absolute_uri(reverse('view_invite', args=[str(obj.uuid)])) + '\n\n'
message += _('If the link does not work use the following code to manually join the space: ') + str(obj.uuid) + '\n\n'
message += _('The invitation is valid until ') + str(obj.valid_until) + '\n\n'
message += _('Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub ') + 'https://github.com/vabene1111/recipes/'
send_mail(
_('Tandoor Recipes Invite'),
message,
None,
[obj.email],
fail_silently=True,
)
except (SMTPException, BadHeaderError, TimeoutError):
pass
return obj
class Meta: class Meta:
model = InviteLink model = InviteLink
fields = ( fields = (
'id', 'uuid', 'email', 'group', 'valid_until', 'used_by', 'created_by', 'created_at',) 'id', 'uuid', 'email', 'group', 'valid_until', 'used_by', 'created_by', 'created_at',)
read_only_fields = ('id', 'uuid', 'email', 'created_by', 'created_at',) read_only_fields = ('id', 'uuid', 'created_by', 'created_at',)
# CORS, REST and Scopes aren't currently working # CORS, REST and Scopes aren't currently working

View File

@ -190,59 +190,3 @@ class MealPlanCreate(GroupRequiredMixin, CreateView, SpaceFormMixing):
return context return context
class InviteLinkCreate(GroupRequiredMixin, CreateView):
groups_required = ['admin']
template_name = "generic/new_template.html"
model = InviteLink
form_class = InviteLinkForm
def form_valid(self, form):
obj = form.save(commit=False)
obj.created_by = self.request.user
# verify given space is actually owned by the user creating the link
if obj.space.created_by != self.request.user:
obj.space = self.request.space
obj.save()
if obj.email:
try:
if InviteLink.objects.filter(space=self.request.space, created_at__gte=datetime.now() - timedelta(hours=4)).count() < 20:
message = _('Hello') + '!\n\n' + _('You have been invited by ') + escape(self.request.user.username)
message += _(' to join their Tandoor Recipes space ') + escape(self.request.space.name) + '.\n\n'
message += _('Click the following link to activate your account: ') + self.request.build_absolute_uri(reverse('view_invite', args=[str(obj.uuid)])) + '\n\n'
message += _('If the link does not work use the following code to manually join the space: ') + str(obj.uuid) + '\n\n'
message += _('The invitation is valid until ') + str(obj.valid_until) + '\n\n'
message += _('Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub ') + 'https://github.com/vabene1111/recipes/'
send_mail(
_('Tandoor Recipes Invite'),
message,
None,
[obj.email],
fail_silently=False,
)
messages.add_message(self.request, messages.SUCCESS,
_('Invite link successfully send to user.'))
else:
messages.add_message(self.request, messages.ERROR,
_('You have send to many emails, please share the link manually or wait a few hours.'))
except (SMTPException, BadHeaderError, TimeoutError):
messages.add_message(self.request, messages.ERROR, _('Email could not be sent to user. Please share the link manually.'))
return HttpResponseRedirect(reverse('view_space'))
def get_context_data(self, **kwargs):
context = super(InviteLinkCreate, self).get_context_data(**kwargs)
context['title'] = _("Invite Link")
return context
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs
def get_initial(self):
return dict(
space=self.request.space,
group=Group.objects.get(name='user')
)

View File

@ -4,16 +4,41 @@
<div class="row mt-2"> <div class="row mt-2">
<div class="col col-12"> <div class="col col-12">
<div v-if="space !== undefined"> <div v-if="space !== undefined">
Recipes {{ space.recipe_count }} / {{ space.max_recipes }} <h6><i class="fas fa-book"></i> {{ $t('Recipes') }}</h6>
Users {{ space.user_count }} / {{ space.max_users }} <b-progress height="1.5rem" :max="space.max_recipes" variant="success" :striped="true">
Files {{ space.file_size_mb }} / {{ space.max_file_storage_mb }} <b-progress-bar :value="space.recipe_count">
{{ space.recipe_count }} /
<template v-if="space.max_recipes === 0"></template>
<template v-else>{{ space.max_recipes }}</template>
</b-progress-bar>
</b-progress>
<h6 class="mt-2"><i class="fas fa-users"></i> {{ $t('Users') }}</h6>
<b-progress height="1.5rem" :max="space.max_users" variant="success" :striped="true">
<b-progress-bar :value="space.user_count">
{{ space.user_count }} /
<template v-if="space.max_users === 0"></template>
<template v-else>{{ space.max_users }}</template>
</b-progress-bar>
</b-progress>
<h6 class="mt-2"><i class="fas fa-file"></i> {{ $t('Files') }}</h6>
<b-progress height="1.5rem" :max="space.max_file_storage_mb" variant="success" :striped="true">
<b-progress-bar :value="space.file_size_mb">
{{ space.file_size_mb }} /
<template v-if="space.max_file_storage_mb === 0"></template>
<template v-else>{{ space.max_file_storage_mb }}</template>
</b-progress-bar>
</b-progress>
</div> </div>
</div> </div>
</div> </div>
<div class="row mt-2"> <div class="row mt-4">
<div class="col col-12"> <div class="col col-12">
<div v-if="user_spaces !== undefined"> <div v-if="user_spaces !== undefined">
<h4 class="mt-2"><i class="fas fa-users"></i> {{ $t('Users') }}</h4>
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
@ -48,15 +73,15 @@
<div class="row mt-2"> <div class="row mt-2">
<div class="col col-12"> <div class="col col-12">
<button @click="show_invite_create = true">Create</button>
<div v-if="invite_links !== undefined"> <div v-if="invite_links !== undefined">
<h4 class="mt-2"><i class="fas fa-users"></i> {{ $t('Invites') }}</h4>
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th>#</th> <th>#</th>
<th>{{ $t('Email') }}</th> <th>{{ $t('Email') }}</th>
<th>{{ $t('Group') }}</th> <th>{{ $t('Group') }}</th>
<th>{{ $t('Token') }}</th> <th></th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
@ -75,7 +100,6 @@
:multiple="false" :multiple="false"
/> />
</td> </td>
<td><input class="form-control" disabled v-model="il.uuid"></td>
<td><input type="date" v-model="il.valid_until" class="form-control"></td> <td><input type="date" v-model="il.valid_until" class="form-control"></td>
<td> <td>
<b-dropdown no-caret right> <b-dropdown no-caret right>
@ -83,20 +107,20 @@
<i class="fas fa-ellipsis-v"></i> <i class="fas fa-ellipsis-v"></i>
</template> </template>
<b-dropdown-item> <!-- <b-dropdown-item>-->
<i class="fas fa-share-alt"></i> <!-- <i class="fas fa-share-alt"></i>-->
<!-- </b-dropdown-item>-->
<b-dropdown-item @click="copyToClipboard(il, true)">
<i class="fas fa-link"></i> {{ $t('Copy Link') }}
</b-dropdown-item> </b-dropdown-item>
<b-dropdown-item> <b-dropdown-item @click="copyToClipboard(il, false)">
<i class="fas fa-link"></i> <i class="far fa-clipboard"></i> {{ $t('Copy Token') }}
</b-dropdown-item> </b-dropdown-item>
<b-dropdown-item> <b-dropdown-item @click="deleteInviteLink(il)">
<i class="far fa-clipboard"></i> <i class="fas fa-trash-alt"></i> {{ $t('Delete') }}
</b-dropdown-item>
<b-dropdown-item>
{{ $t('Delete') }}
</b-dropdown-item> </b-dropdown-item>
@ -105,6 +129,7 @@
</td> </td>
</tr> </tr>
</table> </table>
<b-button variant="primary" @click="show_invite_create = true">{{ $t('Create') }}</b-button>
</div> </div>
</div> </div>
</div> </div>
@ -154,6 +179,13 @@ export default {
this.loadInviteLinks() this.loadInviteLinks()
}, },
methods: { methods: {
copyToClipboard: function (inviteLink, link) {
let content = inviteLink.uuid
if (link) {
content = localStorage.BASE_PATH + this.resolveDjangoUrl('view_invite', inviteLink.uuid)
}
navigator.clipboard.writeText(content)
},
loadInviteLinks: function () { loadInviteLinks: function () {
let apiFactory = new ApiApiFactory() let apiFactory = new ApiApiFactory()
apiFactory.listInviteLinks().then(r => { apiFactory.listInviteLinks().then(r => {
@ -178,7 +210,17 @@ export default {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_DELETE, err) StandardToasts.makeStandardToast(this, StandardToasts.FAIL_DELETE, err)
}) })
} }
} },
deleteInviteLink: function (inviteLink) {
let apiFactory = new ApiApiFactory()
apiFactory.destroyInviteLink(inviteLink.id).then(r => {
this.invite_links = this.invite_links.filter(i => i !== inviteLink)
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_DELETE)
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_DELETE, err)
})
},
}, },
} }
</script> </script>

View File

@ -125,6 +125,8 @@
"Move": "Move", "Move": "Move",
"Merge": "Merge", "Merge": "Merge",
"Parent": "Parent", "Parent": "Parent",
"Copy Link": "Copy Link",
"Copy Token": "Copy Token",
"delete_confirmation": "Are you sure that you want to delete {source}?", "delete_confirmation": "Are you sure that you want to delete {source}?",
"move_confirmation": "Move <i>{child}</i> to parent <i>{parent}</i>", "move_confirmation": "Move <i>{child}</i> to parent <i>{parent}</i>",
"merge_confirmation": "Replace <i>{source}</i> with <i>{target}</i>", "merge_confirmation": "Replace <i>{source}</i> with <i>{target}</i>",
@ -262,6 +264,8 @@
"New_Cookbook": "New cookbook", "New_Cookbook": "New cookbook",
"Hide_Keyword": "Hide keywords", "Hide_Keyword": "Hide keywords",
"Clear": "Clear", "Clear": "Clear",
"Users": "Users",
"Invites": "Invites",
"err_move_self": "Cannot move item to itself", "err_move_self": "Cannot move item to itself",
"nothing": "Nothing to do", "nothing": "Nothing to do",
"err_merge_self": "Cannot merge item with itself", "err_merge_self": "Cannot merge item with itself",