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 gettext import gettext as _
from html import escape
from smtplib import SMTPException
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.http import BadHeaderError
from django.urls import reverse
from django.utils import timezone
from drf_writable_nested import UniqueFieldsMixin, WritableNestedModelSerializer
@ -1032,13 +1036,35 @@ class InviteLinkSerializer(WritableNestedModelSerializer):
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
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:
model = InviteLink
fields = (
'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

View File

@ -190,59 +190,3 @@ class MealPlanCreate(GroupRequiredMixin, CreateView, SpaceFormMixing):
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="col col-12">
<div v-if="space !== undefined">
Recipes {{ space.recipe_count }} / {{ space.max_recipes }}
Users {{ space.user_count }} / {{ space.max_users }}
Files {{ space.file_size_mb }} / {{ space.max_file_storage_mb }}
<h6><i class="fas fa-book"></i> {{ $t('Recipes') }}</h6>
<b-progress height="1.5rem" :max="space.max_recipes" variant="success" :striped="true">
<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 class="row mt-2">
<div class="row mt-4">
<div class="col col-12">
<div v-if="user_spaces !== undefined">
<h4 class="mt-2"><i class="fas fa-users"></i> {{ $t('Users') }}</h4>
<table class="table">
<thead>
<tr>
@ -48,15 +73,15 @@
<div class="row mt-2">
<div class="col col-12">
<button @click="show_invite_create = true">Create</button>
<div v-if="invite_links !== undefined">
<h4 class="mt-2"><i class="fas fa-users"></i> {{ $t('Invites') }}</h4>
<table class="table">
<thead>
<tr>
<th>#</th>
<th>{{ $t('Email') }}</th>
<th>{{ $t('Group') }}</th>
<th>{{ $t('Token') }}</th>
<th></th>
<th></th>
</tr>
</thead>
@ -75,7 +100,6 @@
:multiple="false"
/>
</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>
<b-dropdown no-caret right>
@ -83,20 +107,20 @@
<i class="fas fa-ellipsis-v"></i>
</template>
<b-dropdown-item>
<i class="fas fa-share-alt"></i>
<!-- <b-dropdown-item>-->
<!-- <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>
<i class="fas fa-link"></i>
<b-dropdown-item @click="copyToClipboard(il, false)">
<i class="far fa-clipboard"></i> {{ $t('Copy Token') }}
</b-dropdown-item>
<b-dropdown-item>
<i class="far fa-clipboard"></i>
</b-dropdown-item>
<b-dropdown-item>
{{ $t('Delete') }}
<b-dropdown-item @click="deleteInviteLink(il)">
<i class="fas fa-trash-alt"></i> {{ $t('Delete') }}
</b-dropdown-item>
@ -105,6 +129,7 @@
</td>
</tr>
</table>
<b-button variant="primary" @click="show_invite_create = true">{{ $t('Create') }}</b-button>
</div>
</div>
</div>
@ -154,6 +179,13 @@ export default {
this.loadInviteLinks()
},
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 () {
let apiFactory = new ApiApiFactory()
apiFactory.listInviteLinks().then(r => {
@ -178,7 +210,17 @@ export default {
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>

View File

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