from django import forms from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.models import User from django.db.models import Q from django.utils.translation import gettext_lazy as _ from .models import ApiKey, Attachment, Collection, Item, Kanban, Tag, Team, TeamInvite, UserInvite, UserPreference, Workspace, get_or_create_personal_workspace class MultipleFileInput(forms.ClearableFileInput): allow_multiple_selected = True class MultipleFileField(forms.FileField): def clean(self, data, initial=None): if not data: return [] files = data if isinstance(data, (list, tuple)) else [data] return [super(MultipleFileField, self).clean(file, initial) for file in files] class QuickItemForm(forms.ModelForm): files = MultipleFileField(required=False, widget=MultipleFileInput(attrs={'multiple': True})) class Meta: model = Item fields = ['content'] widgets = {'content': forms.Textarea(attrs={ 'class': 'form-control form-control-lg', 'rows': 3, 'placeholder': _('Write something … /todo /link /public #tag [[note:1]]'), 'x-model': 'text', '@keydown': 'onKeydown($event)', '@input': 'onInput($event)', '@paste': 'handlePaste($event)', })} class EditItemForm(forms.ModelForm): files = MultipleFileField(required=False, widget=MultipleFileInput(attrs={'multiple': True})) class Meta: model = Item fields = ['content', 'comment', 'visibility', 'url'] widgets = { 'comment': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'url': forms.URLInput(attrs={'class': 'form-control', 'placeholder': 'URL'}), } class UserSettingsForm(forms.ModelForm): class Meta: model = User fields = ['first_name', 'last_name', 'email'] widgets = {field: forms.TextInput(attrs={'class': 'form-control'}) for field in ['first_name', 'last_name', 'email']} class UserPreferenceForm(forms.ModelForm): class Meta: model = UserPreference fields = ['language', 'show_notes', 'show_open_todos', 'show_done_todos', 'show_links', 'due_first', 'overview_lines'] labels = { 'language': _('Language'), 'show_notes': _('Show notes'), 'show_open_todos': _('Show open tasks'), 'show_done_todos': _('Show completed tasks'), 'show_links': _('Show links'), 'due_first': _('Always show due items first'), 'overview_lines': _('Lines in overview'), } widgets = { 'language': forms.Select(attrs={'class': 'form-select'}), 'overview_lines': forms.NumberInput(attrs={'class': 'form-control', 'min': 1, 'max': 50}), } class WorkspaceForm(forms.ModelForm): class Meta: model = Workspace fields = ['name', 'slug'] labels = {'name': _('Workspace name'), 'slug': _('Short name')} widgets = { 'name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Company or workspace name')}), 'slug': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'firma'}), } def clean_slug(self): return self.cleaned_data['slug'].strip().lower() class UserInviteForm(forms.ModelForm): email = forms.EmailField(required=True, label=_('Email'), widget=forms.EmailInput(attrs={'class': 'form-control', 'placeholder': 'person@example.com', 'required': True})) class Meta: model = UserInvite fields = ['email'] class InviteRegistrationForm(UserCreationForm): email = forms.EmailField(required=False, label=_('Email'), widget=forms.EmailInput(attrs={'class': 'form-control'})) class Meta: model = User fields = ['username', 'email', 'password1', 'password2'] widgets = {'username': forms.TextInput(attrs={'class': 'form-control'})} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for name in ['password1', 'password2']: self.fields[name].widget.attrs.update({'class': 'form-control'}) class TeamForm(forms.ModelForm): def __init__(self, *args, user=None, workspace=None, **kwargs): super().__init__(*args, **kwargs) self.user = user self.workspace = workspace or (get_or_create_personal_workspace(user) if user and user.is_authenticated else None) class Meta: model = Team fields = ['name', 'slug'] labels = {'name': _('Team name'), 'slug': _('Team tag')} widgets = { 'name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'my2dos'}), 'slug': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'my2dos'}), } def clean_slug(self): slug = self.cleaned_data['slug'].strip().lstrip('#').lower() if self.workspace and Team.objects.filter(workspace=self.workspace, slug=slug).exclude(pk=self.instance.pk).exists(): raise forms.ValidationError(_('This team tag is already used in this workspace.')) return slug def save(self, commit=True): if self.workspace: self.instance.workspace = self.workspace return super().save(commit=commit) class TeamInviteForm(forms.ModelForm): class Meta: model = TeamInvite fields = ['email'] labels = {'email': _('Email')} widgets = {'email': forms.EmailInput(attrs={'class': 'form-control', 'placeholder': 'person@example.com'})} class KanbanForm(forms.ModelForm): tag_name = forms.CharField(label=_('Tag'), widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'my2dos'})) def __init__(self, *args, user=None, workspace=None, **kwargs): super().__init__(*args, **kwargs) self.user = user self.workspace = workspace or (get_or_create_personal_workspace(user) if user and user.is_authenticated else None) class Meta: model = Kanban fields = ['name', 'tag_name'] widgets = {'name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Kanban name')})} def save(self, commit=True): tag_name = self.cleaned_data['tag_name'].strip().lstrip('#').lower() tag, _ = Tag.objects.get_or_create(workspace=self.workspace, name=tag_name) self.instance.workspace = self.workspace self.instance.tag = tag return super().save(commit=commit) class CollectionForm(forms.ModelForm): team = forms.ModelChoiceField(queryset=Team.objects.none(), required=False, label=_('Team'), widget=forms.Select(attrs={'class': 'form-select'})) def __init__(self, *args, user=None, workspace=None, **kwargs): super().__init__(*args, **kwargs) self.fields['visibility'].choices = [ (Item.Visibility.PRIVATE, _('Private')), (Item.Visibility.PUBLIC, _('Public')), ] if user and user.is_authenticated: workspace = self.instance.workspace if self.instance and self.instance.pk else (workspace or get_or_create_personal_workspace(user)) team_ids = user.team_memberships.filter(team__workspace=workspace).values_list('team_id', flat=True) self.fields['team'].queryset = Team.objects.filter(workspace=workspace, pk__in=team_ids).order_by('name') self.fields['team'].initial = self.instance.team_id if self.instance and self.instance.pk else None item_scope = Q(items__workspace=workspace) & (Q(items__owner=user) | Q(items__team_id__in=team_ids)) team_slugs = Team.objects.filter(workspace=workspace).values_list('slug', flat=True) self.fields['filter_tags'].queryset = Tag.objects.filter(workspace=workspace).filter(item_scope).exclude(name__in=team_slugs).distinct().order_by('name') else: self.fields['team'].queryset = Team.objects.none() self.fields['filter_tags'].queryset = Tag.objects.none() def clean(self): cleaned = super().clean() if cleaned.get('mode') == Collection.Mode.TAGS: if not cleaned.get('filter_tags'): self.add_error('filter_tags', _('Select at least one tag for a tag collection.')) if not any(cleaned.get(field) for field in ['include_notes', 'include_todos', 'include_links', 'include_journal']): self.add_error('include_notes', _('Select at least one entry type for a tag collection.')) return cleaned class Meta: model = Collection fields = ['title', 'description', 'visibility', 'mode', 'team', 'filter_tags', 'include_notes', 'include_todos', 'include_links', 'include_journal'] labels = { 'title': _('Title'), 'description': _('Description'), 'visibility': _('Visibility'), 'mode': _('Collection type'), 'filter_tags': _('Content tags'), 'include_notes': _('Notes'), 'include_todos': _('Tasks'), 'include_links': _('Links'), 'include_journal': _('Journal'), } widgets = { 'title': forms.TextInput(attrs={'class': 'form-control'}), 'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}), 'visibility': forms.Select(attrs={'class': 'form-select'}), 'mode': forms.Select(attrs={'class': 'form-select', 'x-model': 'mode'}), 'filter_tags': forms.CheckboxSelectMultiple(), 'include_notes': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'include_todos': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'include_links': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'include_journal': forms.CheckboxInput(attrs={'class': 'form-check-input'}), } class ApiKeyForm(forms.ModelForm): def __init__(self, *args, user=None, workspace=None, **kwargs): super().__init__(*args, **kwargs) if user and user.is_authenticated: workspace = workspace or get_or_create_personal_workspace(user) self.fields['tags'].queryset = Tag.objects.filter(workspace=workspace, items__owner=user).distinct().order_by('name') else: self.fields['tags'].queryset = Tag.objects.none() class Meta: model = ApiKey fields = ['name', 'tags'] widgets = { 'name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('API key name')}), 'tags': forms.CheckboxSelectMultiple(), } class AttachmentForm(forms.ModelForm): class Meta: model = Attachment fields = ['file']