diff --git a/entrypoint.sh b/entrypoint.sh
index 314f51c..0e53b29 100755
--- a/entrypoint.sh
+++ b/entrypoint.sh
@@ -49,10 +49,10 @@ case "$1" in
pip install -r requirements-tests.txt
wait_redis
cd $2
+ export TESTS_RUNNING=1
./manage.py collectstatic --noinput
./manage.py compress
./manage.py compilemessages
- export TESTS_RUNNING=1
py.test -v --cov serviceform/ --cov tasks/ tests/
;;
'bash')
diff --git a/serviceform/__init__.py b/serviceform/__init__.py
index e69de29..b20884d 100644
--- a/serviceform/__init__.py
+++ b/serviceform/__init__.py
@@ -0,0 +1,6 @@
+import pkg_resources
+
+try:
+ __version__ = pkg_resources.get_distribution("serviceform").version
+except pkg_resources.DistributionNotFound:
+ __version__ = 'not installed'
diff --git a/serviceform/serviceform/admin.py b/serviceform/serviceform/admin.py
index 03523e6..dff4f55 100644
--- a/serviceform/serviceform/admin.py
+++ b/serviceform/serviceform/admin.py
@@ -177,38 +177,22 @@ class QuestionInline(ResponsibleMixin, GrappelliSortableHiddenMixin, NestedTabul
fields = ('question', 'responsibles', 'answer_type', 'required', 'order')
-class EmailTemplateInline(NestedStackedInline):
- fields = ('name', 'subject', 'content')
- model = models.EmailTemplate
- extra = 0
-
-
class RevisionInline(NestedStackedInline):
fields = ('name', ('valid_from', 'valid_to'), 'send_emails_after',
- 'send_bulk_email_to_participants')
+ 'send_bulk_email_to_participations')
model = models.FormRevision
extra = 0
-class ResponsibilityPersonInline(NestedStackedInline):
- model = models.ResponsibilityPerson
- extra = 0
- fields = (('forenames', 'surname'), ('email', 'phone_number'), 'street_address',
- ('postal_code', 'city'), 'send_email_notifications', 'hide_contact_details',
- 'show_full_report', 'personal_link')
- readonly_fields = ('personal_link',)
-
-
@admin.register(models.ServiceForm)
class ServiceFormAdmin(OwnerSaveMixin, ExtendedLogMixin, NestedModelAdminMixin,
GuardedModelAdminMixin, admin.ModelAdmin):
class Media:
css = {'all': ('serviceform/serviceform_admin.css',)}
- inlines = [RevisionInline, EmailTemplateInline, ResponsibilityPersonInline,
- Level1CategoryInline, QuestionInline]
+ inlines = [RevisionInline, Level1CategoryInline, QuestionInline]
- superuser_actions = ['bulk_email_former_participants', 'bulk_email_responsibles']
+ superuser_actions = ['bulk_email_former_participations', 'bulk_email_responsibles']
if settings.DEBUG:
superuser_actions.append('shuffle_data')
@@ -227,15 +211,15 @@ class Media:
email_settings = (
'require_email_verification',
- 'verification_email_to_participant',
+ 'verification_email_to_participation',
'email_to_responsibles',
'email_to_invited_users',
- 'email_to_participant',
- 'resend_email_to_participant',
+ 'email_to_participation',
+ 'resend_email_to_participation',
- 'email_to_participant_on_update',
- 'email_to_former_participants',
+ 'email_to_participation_on_update',
+ 'email_to_former_participations',
'bulk_email_to_responsibles',
'email_to_responsible_auth_link',
@@ -262,8 +246,8 @@ class Media:
(_('Ownership'), {'fields': ownership}),
(_('Email settings'), {'fields': email_settings}),
(_('Customization'), {'fields': customization}),
- (_('Ask details from participants'), {'fields': visible_contact_details}),
- (_('Require details from participants'), {'fields': required_contact_details}),
+ (_('Ask details from participations'), {'fields': visible_contact_details}),
+ (_('Require details from participations'), {'fields': required_contact_details}),
)
new_fieldsets = ((_('Basic information'), {'fields': basic}),)
@@ -282,12 +266,12 @@ def get_actions(self, request: HttpRequest):
actions.pop(a, None)
return actions
- def bulk_email_former_participants(self, request: HttpRequest,
+ def bulk_email_former_participations(self, request: HttpRequest,
queryset: Iterable[models.ServiceForm]) -> None:
for serviceform in queryset:
- serviceform.bulk_email_former_participants()
+ serviceform.bulk_email_former_participations()
- bulk_email_former_participants.short_description = _('Bulk email former participants now!')
+ bulk_email_former_participations.short_description = _('Bulk email former participations now!')
def bulk_email_responsibles(self, request: HttpRequest,
queryset: Iterable[models.ServiceForm]) -> None:
@@ -301,7 +285,7 @@ def shuffle_data(self, request: HttpRequest,
for serviceform in queryset:
utils.shuffle_person_data(serviceform)
- shuffle_data.short_description = _('Shuffle participant data')
+ shuffle_data.short_description = _('Shuffle participation data')
def get_queryset(self, request: HttpRequest):
qs = super().get_queryset(request)
@@ -311,13 +295,13 @@ def get_queryset(self, request: HttpRequest):
def get_form(self, request: HttpRequest, obj: models.ServiceForm=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
if obj:
- request._responsibles = responsibles = models.ResponsibilityPerson.objects.filter(
- form=obj)
+ request._responsibles = responsibles = models.Member.objects.filter(
+ organization_id=obj.organization_id)
form.base_fields['responsible'].queryset = responsibles
form.base_fields['current_revision'].queryset = models.FormRevision.objects.filter(
form=obj)
- emailtemplates = models.EmailTemplate.objects.filter(form=obj)
+ emailtemplates = models.EmailTemplate.objects.filter(organization=obj.organization)
for name, field in form.base_fields.items():
if 'email_to' in name:
@@ -353,12 +337,62 @@ class EmailMessageAdmin(ExtendedLogMixin, admin.ModelAdmin):
'content_display',)
-@admin.register(models.Participant)
-class ParticipantAdmin(ExtendedLogMixin, admin.ModelAdmin):
+class MemberInline(NestedStackedInline):
+ model = models.Member
+ extra = 0
+ fields = (('forenames', 'surname'), ('email', 'phone_number'), 'street_address',
+ ('postal_code', 'city'), 'allow_responsible_email',
+ 'allow_participation_email', 'hide_contact_details',
+ 'show_full_report', 'personal_link')
+ readonly_fields = ('personal_link',)
+
+
+class EmailTemplateInline(NestedStackedInline):
+ fields = ('name', 'subject', 'content')
+ model = models.EmailTemplate
+ extra = 0
+
+
+@admin.register(models.Organization)
+class OrganizationAdmin(ExtendedLogMixin, NestedModelAdminMixin, GuardedModelAdminMixin,
+ admin.ModelAdmin):
+ list_display = ('name',)
+ inlines = [MemberInline, EmailTemplateInline]
+ basic = ('name', )
+ new_fieldsets = ((_('Basic information'), {'fields': basic}),)
+
+ def get_form(self, request: HttpRequest, obj: models.Organization=None, **kwargs):
+ form = super().get_form(request, obj, **kwargs)
+ if obj:
+ emailtemplates = models.EmailTemplate.objects.filter(organization=obj)
+
+ for name, field in form.base_fields.items():
+ if 'email_to' in name:
+ field.queryset = emailtemplates
+ if obj and field.queryset:
+ field.required = True
+
+ return form
+
+ def save_model(self, request: HttpRequest, obj: models.Organization, form, change: bool):
+ rv = super().save_model(request, obj, form, change)
+ if not change:
+ obj.create_initial_data()
+ return rv
+
+ def get_fieldsets(self, request: HttpRequest, obj: models.Organization=None):
+ return self.new_fieldsets if obj is None else super().get_fieldsets(request, obj)
+
+ def get_inline_instances(self, request: HttpRequest, obj: models.Organization=None):
+ return super().get_inline_instances(request, obj) if obj else []
+
+
+@admin.register(models.Participation)
+class ParticipationAdmin(ExtendedLogMixin, admin.ModelAdmin):
list_display = (
'id', '__str__', 'form_display', 'form_revision', 'status', 'activities_display',
- 'created_at', 'last_modified', 'personal_link')
- fields = ('forenames', 'surname')
+ 'created_at', 'last_modified',)
+ fields = ('member',)
def get_queryset(self, request):
qs = super().get_queryset(request).select_related('form_revision__form')
diff --git a/serviceform/serviceform/emails.py b/serviceform/serviceform/emails.py
index 668140c..bb6c1a4 100644
--- a/serviceform/serviceform/emails.py
+++ b/serviceform/serviceform/emails.py
@@ -21,7 +21,7 @@
bulk_email_to_responsibles = _("""Dear {{responsible}},
Participation results for {{form}} are now available for you to view.
-You can see all participants for the activities you are responsible of in the following URL:
+You can see all participations for the activities you are responsible of in the following URL:
{{url}}
From now on, you will also receive a notification message, if a new participation submitted to
the areas you are responsible of.You can also adjust your contact details and email notification
@@ -34,7 +34,7 @@
{{contact}}""")
-invite = _("""Dear {{participant}},
+invite = _("""Dear {{participation}},
You are invited to participate in "{{ form }}".
You can fill in your participation details at {{ url }}.
@@ -47,8 +47,8 @@
message_to_responsibles = _("""Dear {{responsible}},
-New participation from {{participant}} has just been submitted to {{form}}.
-You can see all participants for the activities you are responsible of in the following URL:
+New participation from {{participation}} has just been submitted to {{form}}.
+You can see all participations for the activities you are responsible of in the following URL:
{{url}}
You can also adjust your contact details and email notification preferences from that URL.
@@ -58,7 +58,7 @@
Contact person:
{{contact}}""")
-participant_new_form_revision = _("""Dear {{participant}},
+participation_new_form_revision = _("""Dear {{participation}},
New form revision to "{{ form }}" has been published.
Please update your participation information at {{ url }}.
@@ -69,7 +69,7 @@
Contact person:
{{contact}}""")
-participant_on_finish = _("""Dear {{participant}},
+participation_on_finish = _("""Dear {{participation}},
You submitted form "{{ form }}" on {{ last_modified }}.
If you wish to change any of the details you gave, you can go to {{ url }}.
@@ -80,7 +80,7 @@
Contact person:
{{contact}}""")
-resend_email_to_participants = _("""Dear {{participant}},
+resend_email_to_participations = _("""Dear {{participation}},
You submitted form "{{ form }}" on {{ last_modified }}.
If you wish to change any of the details you gave, you can go to {{ url }}.
@@ -91,7 +91,7 @@
Contact person:
{{contact}}""")
-participant_on_update = _("""Dear {{participant}},
+participation_on_update = _("""Dear {{participation}},
You submitted update to your data on form "{{ form }}" on {{ last_modified }}.
If you wish to change any of the details you gave, you can go to {{ url }}.
@@ -105,7 +105,7 @@
request_responsible_auth_link = _("""Dear {{responsible}},
-You can see all participants for the activities you are responsible of in the following URL:
+You can see all participations for the activities you are responsible of in the following URL:
{{url}}
Best regards,
@@ -115,7 +115,7 @@
{{contact}}""")
-verification_email_to_participant = _("""Dear {{participant}},
+verification_email_to_participation = _("""Dear {{participation}},
Your email address needs to be verified. Please do so by clicking link below. Then you can
continue filling the form.
@@ -127,3 +127,16 @@
Contact person:
{{contact}}""")
+
+
+# TODO: check this email content
+email_to_member_auth_link = _("""Dear {{member}},
+
+Here is your link to access your data in {{organization}}:
+{{url}}
+
+Best regards,
+Service form system administrators
+
+Contact person:
+{{contact}}""")
diff --git a/serviceform/serviceform/fields.py b/serviceform/serviceform/fields.py
new file mode 100644
index 0000000..e4eaead
--- /dev/null
+++ b/serviceform/serviceform/fields.py
@@ -0,0 +1,14 @@
+from colorful.fields import RGBColorField
+
+
+class ColorField(RGBColorField):
+ def get_prep_value(self, value: 'ColorStr') -> 'Optional[ColorStr]':
+ rv = super().get_prep_value(value)
+ if rv == '#000000':
+ rv = None
+ return rv
+
+ def from_db_value(self, value: 'Optional[ColorStr]', *args):
+ if value is None:
+ return '#000000'
+ return value
\ No newline at end of file
diff --git a/serviceform/serviceform/forms.py b/serviceform/serviceform/forms.py
index 1e3207f..b52f02e 100644
--- a/serviceform/serviceform/forms.py
+++ b/serviceform/serviceform/forms.py
@@ -63,7 +63,7 @@ def __init__(self, service_form: models.ServiceForm,
request: HttpRequest, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.request = request
- self.instance = service_form
+ self.instance: models.ServiceForm = service_form
helper = self.helper = MyFormHelper(self)
rev_choices = [(rev.name, rev.name) for rev in service_form.formrevision_set.all()]
@@ -77,14 +77,14 @@ def __init__(self, service_form: models.ServiceForm,
helper.layout.append(Submit('submit', _('Save settings')))
def set_initial_data(self) -> None:
- report_settings = utils.get_report_settings(self.request)
+ report_settings = utils.get_report_settings(self.request, self.instance)
for name, f in self.fields.items():
val = report_settings.get(name)
f.initial = val
def save(self) -> None:
report_settings = {name: self.cleaned_data[name] for name in self.fields.keys()}
- utils.set_report_settings(self.request, report_settings)
+ utils.set_report_settings(self.request, self.instance, report_settings)
class PasswordForm(Form):
@@ -108,7 +108,8 @@ def clean_password(self):
return self.cleaned_data
-class ParticipantSendEmailForm(Form):
+#TODO: deprecated, remove
+class ParticipationSendEmailForm(Form):
email = fields.EmailField(max_length=128, label=_('Email'))
def __init__(self, service_form, request, *args, **kwargs):
@@ -122,28 +123,28 @@ def __init__(self, service_form, request, *args, **kwargs):
def clean_email(self):
email = self.cleaned_data['email']
if email and 'email' in self.changed_data:
- participant = models.Participant.objects.filter(
+ participation = models.Participation.objects.filter(
email=email,
form_revision__form=self.instance).first()
- if not participant:
+ if not participation:
raise ValidationError(
_('There were no participation with email address {}').format(email))
return email
def save(self):
- participant = models.Participant.objects.filter(email=self.cleaned_data['email'],
- form_revision__form=self.instance).first()
- success = participant.send_participant_email(models.Participant.EmailIds.RESEND)
+ participation = models.Participation.objects.filter(email=self.cleaned_data['email'],
+ form_revision__form=self.instance).first()
+ success = participation.send_participation_email(models.Participation.EmailIds.RESEND)
if success:
messages.info(self.request,
- _('Access link sent to email address {}').format(participant.email))
+ _('Access link sent to email address {}').format(participation.email))
else:
messages.error(self.request, _('Email could not be sent to email address {}').format(
- participant.email))
+ participation.email))
return success
-class ResponsibleSendEmailForm(Form):
+class MemberSendEmailForm(Form):
email = fields.EmailField(max_length=128, label=_('Email'))
def __init__(self, service_form: models.ServiceForm, request: HttpRequest,
@@ -158,43 +159,44 @@ def __init__(self, service_form: models.ServiceForm, request: HttpRequest,
def clean_email(self) -> str:
email = self.cleaned_data['email']
if email and 'email' in self.changed_data:
- responsible = models.ResponsibilityPerson.objects.filter(email=email,
- form=self.instance).first()
- if not responsible:
+ member = models.Member.objects.filter(
+ email=email, organization=self.instance.organization).first()
+ if not member:
raise ValidationError(
- _('There were no responsible with email address {}').format(email))
+ _('There were no user with email address {}').format(email))
return email
def save(self) -> Optional[models.EmailMessage]:
- responsible = models.ResponsibilityPerson.objects.filter(email=self.cleaned_data['email'],
- form=self.instance).first()
- success = responsible.resend_auth_link()
+ member = models.Member.objects.filter(
+ email=self.cleaned_data['email'], organization=self.instance.organization).first()
+ success = member.resend_auth_link()
if success:
messages.info(self.request,
- _('Access link sent to email address {}').format(responsible.email))
+ _('Access link sent to email address {}').format(member.email))
else:
messages.error(self.request, _('Email could not be sent to email address {}').format(
- responsible.email))
+ member.email))
return success
class ContactForm(ModelForm):
class Meta:
- model = models.Participant
+ model = models.Member
fields = ('forenames', 'surname', 'year_of_birth', 'street_address',
- 'postal_code', 'city', 'email', 'phone_number', 'send_email_allowed')
+ 'postal_code', 'city', 'email', 'phone_number', "allow_participation_email")
- def __init__(self, *args, user: 'AbstractUser'=None, **kwargs) -> None:
+ def __init__(self, *args, serviceform: models.ServiceForm=None, user: 'AbstractUser'=None,
+ **kwargs) -> None:
super().__init__(*args, **kwargs)
- self.participant = self.instance
- self.service_form = self.participant.form
+ self.member = self.instance
+ self.serviceform = serviceform
self.user = user
self.helper = helper = MyFormHelper(self)
self._fix_fields()
helper.form_id = 'contactform'
- if self.service_form.is_published:
+ if self.serviceform.is_published:
helper.layout.append(
Submit('submit', _('Continue'), css_class='btn-participation-continue'))
else:
@@ -202,29 +204,30 @@ def __init__(self, *args, user: 'AbstractUser'=None, **kwargs) -> None:
Submit('submit', _('Save details'), css_class='btn-participation-continue'))
def _fix_fields(self) -> None:
- req = self.service_form.required_street_address
+ req = self.serviceform.required_street_address
self.fields['street_address'].required = req
self.fields['postal_code'].required = req
self.fields['city'].required = req
- if not self.service_form.visible_street_address:
+ if not self.serviceform.visible_street_address:
del self.fields['street_address']
del self.fields['postal_code']
del self.fields['city']
- req = self.service_form.required_year_of_birth
+ req = self.serviceform.required_year_of_birth
if not req:
self.fields['year_of_birth'].help_text = _('Optional')
self.fields['year_of_birth'].required = req
- if not self.service_form.visible_year_of_birth:
+ if not self.serviceform.visible_year_of_birth:
del self.fields['year_of_birth']
- self.fields['phone_number'].required = self.service_form.required_phone_number
- if not self.service_form.visible_phone_number:
+ self.fields['phone_number'].required = self.serviceform.required_phone_number
+ if not self.serviceform.visible_phone_number:
del self.fields['phone_number']
- if utils.user_has_serviceform_permission(self.user, self.service_form,
+ self.fields['email'].required = True
+ if utils.user_has_serviceform_permission(self.user, self.serviceform,
raise_permissiondenied=False):
self.fields['email'].required = False
@@ -240,37 +243,43 @@ def clean_year_of_birth(self):
def clean(self):
cleaned_data = super().clean()
- if ('email' not in self.errors and not self.fields['email'].required and cleaned_data.get(
- 'send_email_allowed')
- and not cleaned_data.get('email')):
+ if ('email' not in self.errors
+ and not self.fields['email'].required
+ and cleaned_data.get('allow_participation_email')
+ and not cleaned_data.get('email')):
raise ValidationError(_('If sending email is allowed email address need to be given'))
return cleaned_data
def clean_email(self):
email = self.cleaned_data['email']
- if email and 'email' in self.changed_data and \
- models.Participant.objects.filter(email=email,
- form_revision__form=self.service_form) \
- .exclude(pk=self.participant.pk):
+ if (email and 'email' in self.changed_data
+ and models.Member.objects.filter(email=email,
+ organization_id=self.serviceform.organization_id)
+ .exclude(pk=self.member.pk)):
logger.info('User tried to enter same email address %s again.', email)
email_link = '{}'.format(reverse('send_auth_link', args=(email,)),
_('resend auth link to your email!'))
+ # TODO: make sure user can enter participation even if there is only member but no
+ # participation for the form. Email should contain some postfix that identifies
+ # the form from which this was sent.
raise ValidationError(
- mark_safe(_('There is already participation with this email address. '
- 'To edit earlier participation, {}').format(email_link)))
+ mark_safe(_('There is already member with this email address in the system. '
+ 'To proceed in entering participation '
+ 'details, {}').format(email_link)))
return email
def save(self, commit: bool=True):
if 'email' in self.changed_data:
self.instance.email_verified = False
+ self.instance.organization_id = self.serviceform.organization_id
return super().save(commit=commit)
class ResponsibleForm(ModelForm):
class Meta:
- model = models.ResponsibilityPerson
+ model = models.Member
fields = ('forenames', 'surname', 'street_address',
- 'postal_code', 'city', 'email', 'phone_number', 'send_email_notifications')
+ 'postal_code', 'city', 'email', 'phone_number', "allow_responsible_email")
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
@@ -282,13 +291,13 @@ def __init__(self, *args, **kwargs) -> None:
class LogForm(ModelForm):
class Meta:
- model = models.ParticipantLog
+ model = models.ParticipationLog
fields = ('message',)
- def __init__(self, participant: models.Participant, user: 'AbstractUser',
+ def __init__(self, participation: models.Participation, user: 'AbstractUser',
*args, **kwargs) -> None:
super().__init__(*args, **kwargs)
- self.instance.participant = participant
+ self.instance.participation = participation
self.instance.written_by = user
@@ -308,10 +317,10 @@ class ParticipationForm:
Not any standard Django form.
"""
- def __init__(self, request: HttpRequest, participant: models.Participant,
+ def __init__(self, request: HttpRequest, participation: models.Participation,
category: models.Level1Category=None, post_data: 'QueryDict'=None,
service_form: models.ServiceForm=None) -> None:
- self.instance = participant
+ self.instance = participation
self.request = request
self.post_data = post_data
self.all_activities = {}
@@ -324,12 +333,12 @@ def __init__(self, request: HttpRequest, participant: models.Participant,
None), 'Counters are not yet initialized!'
self.category = category
self._fetch_instances()
- if participant and not post_data:
+ if participation and not post_data:
self.load()
def load(self) -> None:
- participant = self.instance
- for pact in participant.participationactivity_set.all():
+ participation = self.instance
+ for pact in participation.participationactivity_set.all():
act = self.all_activities.get(pact.activity_id)
if not act:
continue
@@ -344,23 +353,23 @@ def load(self) -> None:
self.selected_choices.add(choice)
def save(self) -> None:
- participant = self.instance
+ participation = self.instance
with transaction.atomic():
for choice in self.selected_choices:
self.selected_activities.add(choice.activity)
- participant.participationactivity_set.filter(
+ participation.participationactivity_set.filter(
activity_id__in=self.all_activities.keys()).exclude(
activity__in=self.selected_activities).delete()
for act in self.selected_activities:
pact, created = models.ParticipationActivity.objects.get_or_create(
- participant=participant, activity=act)
+ participation=participation, activity=act)
pact.additional_info = getattr(act, 'extra', None)
pact.save(update_fields=['additional_info'])
- models.ParticipationActivityChoice.objects.filter(activity__participant=participant) \
+ models.ParticipationActivityChoice.objects.filter(activity__participation=participation) \
.filter(activity_choice_id__in=self.all_choices.keys()) \
.exclude(activity_choice__in=self.selected_choices).delete()
for choice in self.selected_choices:
- pact = participant.participationactivity_set.get(activity_id=choice.activity_id)
+ pact = participation.participationactivity_set.get(activity_id=choice.activity_id)
pchoice, created = models.ParticipationActivityChoice.objects.get_or_create(
activity=pact,
activity_choice=choice)
@@ -454,9 +463,9 @@ class QuestionForm:
Not any standard Django form.
"""
- def __init__(self, request: HttpRequest, participant: models.Participant,
+ def __init__(self, request: HttpRequest, participation: models.Participation,
post_data: 'QueryDict'=None) -> None:
- self.instance = participant
+ self.instance = participation
self.request = request
self.data = post_data
self.questions = {}
@@ -471,18 +480,18 @@ def _read_questions(self):
self.questions[q.pk] = q
def load(self):
- participant = self.instance
- for q in participant.questionanswer_set.all():
+ participation = self.instance
+ for q in participation.questionanswer_set.all():
question = self.questions[q.question_id]
question.answer = q.answer
def save(self):
- participant = self.instance
+ participation = self.instance
with transaction.atomic():
with_answer = {q for q in self.questions.values() if getattr(q, 'answer', None)}
- participant.questionanswer_set.exclude(question__in=with_answer).delete()
+ participation.questionanswer_set.exclude(question__in=with_answer).delete()
for q in with_answer:
- q_a, created = models.QuestionAnswer.objects.get_or_create(participant=participant,
+ q_a, created = models.QuestionAnswer.objects.get_or_create(participation=participation,
question=q)
answer = getattr(q, 'answer', '')
if q_a.answer != answer:
@@ -527,12 +536,12 @@ def clean(self):
def __str__(self):
return render_to_string('serviceform/participation/question_form/question_form.html',
- {'participant': self.instance}, request=self.request)
+ {'participation': self.instance}, request=self.request)
class InviteForm(Form):
- old_participants = fields.BooleanField(required=False, label=_(
- 'Send invitations also to participants that have participated in older form versions '
+ old_participations = fields.BooleanField(required=False, label=_(
+ 'Send invitations also to participations that have participated in older form versions '
'but not yet this form'))
email_addresses = fields.CharField(widget=widgets.Textarea, required=True, label=_(
'Email addresses, separated by comma, space or enter'))
@@ -566,10 +575,10 @@ def clean_email_addresses(self):
def save(self, request: HttpRequest=None) -> None:
addresses = self.address_list(self.cleaned_data.get('email_addresses', ''))
- old_participants = self.cleaned_data.get('old_participants')
+ old_participations = self.cleaned_data.get('old_participations')
for a in addresses:
InviteUserResponse = models.ServiceForm.InviteUserResponse
- response = self.service_form.invite_user(a, old_participants=old_participants)
+ response = self.service_form.invite_user(a, old_participations=old_participations)
if response == InviteUserResponse.EMAIL_SENT:
messages.info(request, _('Invitation sent to {}').format(a))
elif response == InviteUserResponse.USER_EXISTS:
diff --git a/serviceform/serviceform/locale/en/LC_MESSAGES/django.po b/serviceform/serviceform/locale/en/LC_MESSAGES/django.po
index ba480b1..e7ac350 100644
--- a/serviceform/serviceform/locale/en/LC_MESSAGES/django.po
+++ b/serviceform/serviceform/locale/en/LC_MESSAGES/django.po
@@ -68,11 +68,11 @@ msgid "Customization"
msgstr ""
#: admin.py:264
-msgid "Ask details from participants"
+msgid "Ask details from participations"
msgstr ""
#: admin.py:265
-msgid "Require details from participants"
+msgid "Require details from participations"
msgstr ""
#: admin.py:289
@@ -80,7 +80,7 @@ msgid "Copy form as a new"
msgstr ""
#: admin.py:295
-msgid "Bulk email former participants now!"
+msgid "Bulk email former participations now!"
msgstr ""
#: admin.py:301
@@ -88,7 +88,7 @@ msgid "Bulk email responsibility persons now!"
msgstr ""
#: admin.py:307
-msgid "Shuffle participant data"
+msgid "Shuffle participation data"
msgstr ""
#: admin.py:342
@@ -111,7 +111,7 @@ msgid ""
"Dear {{responsible}},\n"
"\n"
"Participation results for {{form}} are now available for you to view.\n"
-"You can see all participants for the activities you are responsible of in "
+"You can see all participations for the activities you are responsible of in "
"the following URL:\n"
"{{url}}\n"
"From now on, you will also receive a notification message, if a new "
@@ -129,7 +129,7 @@ msgstr ""
#: emails.py:37
msgid ""
-"Dear {{participant}},\n"
+"Dear {{participation}},\n"
"\n"
"You are invited to participate in \"{{ form }}\".\n"
"You can fill in your participation details at {{ url }}.\n"
@@ -145,8 +145,8 @@ msgstr ""
msgid ""
"Dear {{responsible}},\n"
"\n"
-"New participation from {{participant}} has just been submitted to {{form}}.\n"
-"You can see all participants for the activities you are responsible of in "
+"New participation from {{participation}} has just been submitted to {{form}}.\n"
+"You can see all participations for the activities you are responsible of in "
"the following URL:\n"
"{{url}}\n"
"You can also adjust your contact details and email notification preferences "
@@ -161,7 +161,7 @@ msgstr ""
#: emails.py:61
msgid ""
-"Dear {{participant}},\n"
+"Dear {{participation}},\n"
"\n"
"New form revision to \"{{ form }}\" has been published.\n"
"Please update your participation information at {{ url }}.\n"
@@ -175,7 +175,7 @@ msgstr ""
#: emails.py:72 emails.py:83
msgid ""
-"Dear {{participant}},\n"
+"Dear {{participation}},\n"
"\n"
"You submitted form \"{{ form }}\" on {{ last_modified }}.\n"
"If you wish to change any of the details you gave, you can go to {{ url }}.\n"
@@ -189,7 +189,7 @@ msgstr ""
#: emails.py:94
msgid ""
-"Dear {{participant}},\n"
+"Dear {{participation}},\n"
"\n"
"You submitted update to your data on form \"{{ form }}\" on "
"{{ last_modified }}.\n"
@@ -206,7 +206,7 @@ msgstr ""
msgid ""
"Dear {{responsible}},\n"
"\n"
-"You can see all participants for the activities you are responsible of in "
+"You can see all participations for the activities you are responsible of in "
"the following URL:\n"
"{{url}}\n"
"\n"
@@ -219,7 +219,7 @@ msgstr ""
#: emails.py:118
msgid ""
-"Dear {{participant}},\n"
+"Dear {{participation}},\n"
"\n"
"Your email address needs to be verified. Please do so by clicking link "
"below. Then you can\n"
@@ -360,7 +360,7 @@ msgstr ""
#: forms.py:521
msgid ""
-"Send invitations also to participants that have participated in older form "
+"Send invitations also to participations that have participated in older form "
"versions but not yet this form"
msgstr ""
@@ -504,12 +504,12 @@ msgid "Valid to"
msgstr ""
#: models.py:315
-msgid "Send bulk email to participants"
+msgid "Send bulk email to participations"
msgstr ""
#: models.py:316
msgid ""
-"Send email to participants that filled the form when this revision was "
+"Send email to participations that filled the form when this revision was "
"active. Email is sent when new current revision is published."
msgstr ""
@@ -546,7 +546,7 @@ msgstr ""
#: models.py:408
msgid ""
"Following context may (depending on topic) be available for both subject and "
-"content: {{responsible}}, {{participant}}, {{last_modified}}, {{form}}, "
+"content: {{responsible}}, {{participation}}, {{last_modified}}, {{form}}, "
"{{url}}, {{contact}}"
msgstr ""
@@ -587,12 +587,12 @@ msgid "Require email verification"
msgstr ""
#: models.py:449
-msgid "Verification email to participant"
+msgid "Verification email to participation"
msgstr ""
#: models.py:451
msgid ""
-"Email verification message that is sent to participant when filling form, if "
+"Email verification message that is sent to participation when filling form, if "
"email verification is enabled"
msgstr ""
@@ -621,37 +621,37 @@ msgid "Email that is sent to responsible when he requests auth link"
msgstr ""
#: models.py:483
-msgid "Email to participant, on finish"
+msgid "Email to participation, on finish"
msgstr ""
#: models.py:484
msgid ""
-"Email that is sent to participant after he has fulfilled his participation"
+"Email that is sent to participation after he has fulfilled his participation"
msgstr ""
#: models.py:489
-msgid "Email to participant, on update"
+msgid "Email to participation, on update"
msgstr ""
#: models.py:490
msgid ""
-"Email that is sent to participant after he has updated his participation"
+"Email that is sent to participation after he has updated his participation"
msgstr ""
#: models.py:496
-msgid "Resend email to participant"
+msgid "Resend email to participation"
msgstr ""
#: models.py:497
-msgid "Email that is sent to participant if he requests resending email"
+msgid "Email that is sent to participation if he requests resending email"
msgstr ""
#: models.py:503
-msgid "Bulk email to former participants"
+msgid "Bulk email to former participations"
msgstr ""
#: models.py:504
-msgid "Email that is sent to former participants when form is published"
+msgid "Email that is sent to former participations when form is published"
msgstr ""
#: models.py:510
@@ -674,7 +674,7 @@ msgid ""
msgstr ""
#: models.py:526
-msgid "Password that is asked from participants"
+msgid "Password that is asked from participations"
msgstr ""
#: models.py:530
@@ -780,7 +780,7 @@ msgid "New participation arrived for form {{form}}"
msgstr ""
#: models.py:626
-msgid "Default email to participant, on finish"
+msgid "Default email to participation, on finish"
msgstr ""
#: models.py:628
@@ -788,7 +788,7 @@ msgid "Your update to form {{form}}"
msgstr ""
#: models.py:632
-msgid "Default email to participant, on update"
+msgid "Default email to participation, on update"
msgstr ""
#: models.py:634
@@ -796,7 +796,7 @@ msgid "Your updated participation to form {{form}}"
msgstr ""
#: models.py:638
-msgid "Default email to former participants"
+msgid "Default email to former participations"
msgstr ""
#: models.py:640
@@ -804,7 +804,7 @@ msgid "New form revision to form {{form}} has been published"
msgstr ""
#: models.py:644
-msgid "Default resend email to participant"
+msgid "Default resend email to participation"
msgstr ""
#: models.py:646
@@ -812,7 +812,7 @@ msgid "Your participation to form {{form}}"
msgstr ""
#: models.py:650
-msgid "Default invite email to participants"
+msgid "Default invite email to participations"
msgstr ""
#: models.py:652
@@ -828,7 +828,7 @@ msgid "Your report in {{form}}"
msgstr ""
#: models.py:662
-msgid "Default verification email to participant"
+msgid "Default verification email to participation"
msgstr ""
#: models.py:664
@@ -904,7 +904,7 @@ msgid "Level 2 categories"
msgstr ""
#: models.py:859 templates/serviceform/participation/preview_view.html:33
-#: templates/serviceform/reports/view_participant.html:25
+#: templates/serviceform/reports/view_participation.html:25
msgid "Activity"
msgstr ""
@@ -938,7 +938,7 @@ msgstr ""
#: models.py:930 models.py:947
#: templates/serviceform/participation/preview_view.html:73
-#: templates/serviceform/reports/view_participant.html:60
+#: templates/serviceform/reports/view_participation.html:60
msgid "Question"
msgstr ""
@@ -975,11 +975,11 @@ msgid "Answer required?"
msgstr ""
#: models.py:973
-msgid "Participant"
+msgid "Participation"
msgstr ""
#: models.py:974 urls.py:90
-msgid "Participants"
+msgid "Participations"
msgstr ""
#: models.py:1001
@@ -1024,7 +1024,7 @@ msgid ""
msgstr ""
#: models.py:1041
-msgid "Participant created in system"
+msgid "Participation created in system"
msgstr ""
#: models.py:1044
@@ -1035,7 +1035,7 @@ msgstr ""
#: templates/serviceform/participation/preview_view.html:89
#: templates/serviceform/reports/contents/_all_questions.html:13
#: templates/serviceform/reports/contents/_responsible_contents.html:62
-#: templates/serviceform/reports/view_participant.html:76
+#: templates/serviceform/reports/view_participation.html:76
msgid "No"
msgstr ""
@@ -1043,7 +1043,7 @@ msgstr ""
#: templates/serviceform/participation/preview_view.html:87
#: templates/serviceform/reports/contents/_all_questions.html:11
#: templates/serviceform/reports/contents/_responsible_contents.html:60
-#: templates/serviceform/reports/view_participant.html:74
+#: templates/serviceform/reports/view_participation.html:74
msgid "Yes"
msgstr ""
@@ -1085,7 +1085,7 @@ msgid ""
"If this is the first time you fill this form, you need a password to continue"
msgstr ""
-#: templates/serviceform/login/send_participant_auth_link.html:5
+#: templates/serviceform/login/send_participation_auth_link.html:5
msgid ""
"If you have filled this form already in the past, please give your email\n"
" below so we can send you a personal link to update your participation "
@@ -1099,16 +1099,16 @@ msgid ""
" we can send you a personal link to view your participation report."
msgstr ""
-#: templates/serviceform/login/unsubscribe_participant.html:4
-msgid "You won't be getting any emails (as participant for "
+#: templates/serviceform/login/unsubscribe_participation.html:4
+msgid "You won't be getting any emails (as participation for "
msgstr ""
-#: templates/serviceform/login/unsubscribe_participant.html:4
-#: templates/serviceform/login/unsubscribe_responsible.html:4
+#: templates/serviceform/login/unsubscribe_participation.html:4
+#: templates/serviceform/login/unsubscribe_member.html:4
msgid ") from this system any more."
msgstr ""
-#: templates/serviceform/login/unsubscribe_responsible.html:4
+#: templates/serviceform/login/unsubscribe_member.html:4
msgid "You won't be getting any emails (as responsible for "
msgstr ""
@@ -1179,7 +1179,7 @@ msgstr ""
#: templates/serviceform/participation/participation_form/snippets/responsible_tooltip.html:7
#: templates/serviceform/participation/preview_view.html:8
-#: templates/serviceform/reports/view_participant.html:6 urls.py:73
+#: templates/serviceform/reports/view_participation.html:6 urls.py:73
msgid "Contact details"
msgstr ""
@@ -1230,13 +1230,13 @@ msgid "Change contact details"
msgstr ""
#: templates/serviceform/participation/preview_view.html:30
-#: templates/serviceform/reports/view_participant.html:22
+#: templates/serviceform/reports/view_participation.html:22
msgid "I am wishing to participate in the following activities"
msgstr ""
#: templates/serviceform/participation/preview_view.html:36
-#: templates/serviceform/reports/snippets/_participant_row.html:59
-#: templates/serviceform/reports/view_participant.html:28
+#: templates/serviceform/reports/snippets/_participation_row.html:59
+#: templates/serviceform/reports/view_participation.html:28
msgid "Additional info"
msgstr ""
@@ -1247,12 +1247,12 @@ msgstr ""
#: templates/serviceform/participation/preview_view.html:70
#: templates/serviceform/reports/contents/_all_questions.html:2
#: templates/serviceform/reports/contents/_responsible_contents.html:50
-#: templates/serviceform/reports/view_participant.html:57
+#: templates/serviceform/reports/view_participation.html:57
msgid "Answers to questions"
msgstr ""
#: templates/serviceform/participation/preview_view.html:76
-#: templates/serviceform/reports/view_participant.html:63
+#: templates/serviceform/reports/view_participation.html:63
msgid "Answer"
msgstr ""
@@ -1305,8 +1305,8 @@ msgstr ""
msgid "All activities"
msgstr ""
-#: templates/serviceform/reports/contents/_all_participants.html:2
-msgid "All participants"
+#: templates/serviceform/reports/contents/_all_participations.html:2
+msgid "All participations"
msgstr ""
#: templates/serviceform/reports/contents/_all_responsibles.html:2
@@ -1354,7 +1354,7 @@ msgid "Settings"
msgstr ""
#: templates/serviceform/reports/snippets/_help.html:3
-msgid "Each participant rows contains the following data"
+msgid "Each participation rows contains the following data"
msgstr ""
#: templates/serviceform/reports/snippets/_help.html:7
@@ -1383,20 +1383,20 @@ msgstr ""
msgid "Notes have been entered"
msgstr ""
-#: templates/serviceform/reports/snippets/_participant_row.html:32
+#: templates/serviceform/reports/snippets/_participation_row.html:32
msgid "Number of activities"
msgstr ""
-#: templates/serviceform/reports/snippets/_participant_row.html:64
-#: templates/serviceform/reports/view_participant.html:104
+#: templates/serviceform/reports/snippets/_participation_row.html:64
+#: templates/serviceform/reports/view_participation.html:104
msgid "Log"
msgstr ""
-#: templates/serviceform/reports/view_participant.html:88
+#: templates/serviceform/reports/view_participation.html:88
msgid "Other information"
msgstr ""
-#: templates/serviceform/reports/view_participant.html:113
+#: templates/serviceform/reports/view_participation.html:113
msgid "Add entry"
msgstr ""
diff --git a/serviceform/serviceform/locale/fi/LC_MESSAGES/django.po b/serviceform/serviceform/locale/fi/LC_MESSAGES/django.po
index 6cad713..563ffb3 100644
--- a/serviceform/serviceform/locale/fi/LC_MESSAGES/django.po
+++ b/serviceform/serviceform/locale/fi/LC_MESSAGES/django.po
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-07-30 15:20+0300\n"
-"PO-Revision-Date: 2017-07-30 15:21+0300\n"
+"POT-Creation-Date: 2017-05-19 12:57+0300\n"
+"PO-Revision-Date: 2017-05-19 12:57+0300\n"
"Last-Translator: Tuomas Airaksinen \n"
"Language-Team: \n"
"Language: fi\n"
@@ -19,76 +19,80 @@ msgstr ""
"X-Generator: Poedit 1.8.7.1\n"
#. Translators: This appears after "Changed" and is joined with "and"
-#: serviceform/admin.py:107
+#: admin.py:106
#, python-brace-format
msgid " {label} to {new_value} (was {old_value}) "
msgstr " {label} muutettiin arvoksi {new_value} (oli {old_value}) "
-#: serviceform/admin.py:108
+#: admin.py:107
msgid "and"
msgstr "ja"
-#: serviceform/admin.py:116
+#: admin.py:115
#, python-format
msgid "Changed %s."
msgstr "Muokattu %s."
-#: serviceform/admin.py:125
+#: admin.py:124
#, python-format
msgid "Added %(name)s \"%(object)s\"."
msgstr "Lisätty %(name)s \"%(object)s\"."
-#: serviceform/admin.py:131
+#: admin.py:130
#, python-format
msgid "Changed %(list)s for %(name)s \"%(object)s\"."
msgstr "Muokattu %(list)s kohteelle %(name)s \"%(object)s\"."
-#: serviceform/admin.py:137
+#: admin.py:136
#, python-format
msgid "Deleted %(name)s \"%(object)s\"."
msgstr "Poistettu %(name)s \"%(object)s\"."
-#: serviceform/admin.py:141
+#: admin.py:140
msgid "No fields changed."
msgstr "Ei muutoksia kenttiin."
-#: serviceform/admin.py:261 serviceform/admin.py:269
+#: admin.py:260 admin.py:268
msgid "Basic information"
msgstr "Perustiedot"
-#: serviceform/admin.py:262
+#: admin.py:261
msgid "Ownership"
msgstr "Omistajuus"
-#: serviceform/admin.py:263
+#: admin.py:262
msgid "Email settings"
msgstr "Sähköpostiasetukset"
-#: serviceform/admin.py:264
+#: admin.py:263
msgid "Customization"
msgstr "Lomakkeen asetukset"
-#: serviceform/admin.py:265
-msgid "Ask details from participants"
+#: admin.py:264
+msgid "Ask details from participations"
msgstr "Kysy nämä tiedot osallistujilta"
-#: serviceform/admin.py:266
-msgid "Require details from participants"
+#: admin.py:265
+msgid "Require details from participations"
msgstr "Vaadi nämä tiedot osallistujilta"
-#: serviceform/admin.py:290
-msgid "Bulk email former participants now!"
+#: admin.py:289
+msgid "Copy form as a new"
+msgstr "Kopioi lomake uudeksi"
+
+#: admin.py:295
+msgid "Bulk email former participations now!"
msgstr "Lähetä massasähköpostia aikaisemmille osallistujille nyt!"
-#: serviceform/admin.py:297
+#: admin.py:301
msgid "Bulk email responsibility persons now!"
msgstr "Lähetä massasähköpostia vastuuhenkilöille nyt!"
-#: serviceform/admin.py:304
-msgid "Shuffle participant data"
+#: admin.py:307
+msgid "Shuffle participation data"
msgstr "Sekoita henkilötiedot"
-#: serviceform/admin.py:339
+#: admin.py:342
msgid ""
"You have {} level 1 categories. We recommend that no more than 7 level 1 "
"categories are used, if form flow is split to categories, so that form is "
@@ -99,16 +103,20 @@ msgstr ""
"osallistumislomake tason 1 kategorioiden mukaisesti osiin\" -toiminto on "
"käytössä."
-#: serviceform/apps.py:25
+#: admin.py:363
+msgid "Send mail"
+msgstr "Lähetä sähköpostia"
+
+#: apps.py:25
msgid "Service form application"
msgstr "Palvelulomake"
-#: serviceform/emails.py:21
+#: emails.py:21
msgid ""
"Dear {{responsible}},\n"
"\n"
"Participation results for {{form}} are now available for you to view.\n"
-"You can see all participants for the activities you are responsible of in "
+"You can see all participations for the activities you are responsible of in "
"the following URL:\n"
"{{url}}\n"
"From now on, you will also receive a notification message, if a new "
@@ -139,9 +147,9 @@ msgstr ""
"Yhteyshenkilö: \n"
"{{contact}}"
-#: serviceform/emails.py:37
+#: emails.py:37
msgid ""
-"Dear {{participant}},\n"
+"Dear {{participation}},\n"
"\n"
"You are invited to participate in \"{{ form }}\".\n"
"You can fill in your participation details at {{ url }}.\n"
@@ -152,7 +160,7 @@ msgid ""
"Contact person:\n"
"{{contact}}"
msgstr ""
-"Hei {{participant}},\n"
+"Hei {{participation}},\n"
"\n"
"Sinut on kutsuttu täyttämään lomake \"{{form}}\".\n"
"Voit kertoa halukkuudestasi osallistua {{url}}.\n"
@@ -163,12 +171,12 @@ msgstr ""
"Yhteyshenkilö:\n"
"{{contact}}"
-#: serviceform/emails.py:48
+#: emails.py:48
msgid ""
"Dear {{responsible}},\n"
"\n"
-"New participation from {{participant}} has just been submitted to {{form}}.\n"
-"You can see all participants for the activities you are responsible of in "
+"New participation from {{participation}} has just been submitted to {{form}}.\n"
+"You can see all participations for the activities you are responsible of in "
"the following URL:\n"
"{{url}}\n"
"You can also adjust your contact details and email notification preferences "
@@ -182,7 +190,7 @@ msgid ""
msgstr ""
"Hei {{responsible}}, \n"
"\n"
-"Uusi osallistujatieto henkilöltä {{participant}} on tallennettu lomakkeeseen "
+"Uusi osallistujatieto henkilöltä {{participation}} on tallennettu lomakkeeseen "
"{{form}}. \n"
"Voit tarkastella osallistujatietoja niihin tehtäviin joista olet vastuussa "
"osoitteessa:\n"
@@ -196,9 +204,9 @@ msgstr ""
"Yhteyshenkilö: \n"
"{{contact}}"
-#: serviceform/emails.py:61
+#: emails.py:61
msgid ""
-"Dear {{participant}},\n"
+"Dear {{participation}},\n"
"\n"
"New form revision to \"{{ form }}\" has been published.\n"
"Please update your participation information at {{ url }}.\n"
@@ -209,7 +217,7 @@ msgid ""
"Contact person:\n"
"{{contact}}"
msgstr ""
-"Hei {{participant}},\n"
+"Hei {{participation}},\n"
"\n"
"Uusi versio lomakkeesta \"{{form}}\" on julkaistu.\n"
"Päivitä osallistumistietosi osoitteessa {{url}}.\n"
@@ -220,9 +228,9 @@ msgstr ""
"Yhteyshenkilö:\n"
"{{contact}}"
-#: serviceform/emails.py:72 serviceform/emails.py:83
+#: emails.py:72 emails.py:83
msgid ""
-"Dear {{participant}},\n"
+"Dear {{participation}},\n"
"\n"
"You submitted form \"{{ form }}\" on {{ last_modified }}.\n"
"If you wish to change any of the details you gave, you can go to {{ url }}.\n"
@@ -233,7 +241,7 @@ msgid ""
"Contact person:\n"
"{{contact}}"
msgstr ""
-"Hei {{participant}},\n"
+"Hei {{participation}},\n"
"\n"
"Lähetit tiedot lomakkeeseen \"{{form}}\" ajankohtana {{last_modified}}.\n"
"Jos haluat muuttaa antamiasi tietoja, voit mennä osoitteeseen {{url}}.\n"
@@ -244,9 +252,9 @@ msgstr ""
"Yhteyshenkilö:\n"
"{{contact}}"
-#: serviceform/emails.py:94
+#: emails.py:94
msgid ""
-"Dear {{participant}},\n"
+"Dear {{participation}},\n"
"\n"
"You submitted update to your data on form \"{{ form }}\" on "
"{{ last_modified }}.\n"
@@ -258,7 +266,7 @@ msgid ""
"Contact person:\n"
"{{contact}}"
msgstr ""
-"Hei {{participant}},\n"
+"Hei {{participation}},\n"
"\n"
"Päivitit tietojasi lomakkeeseen \"{{form}}\" ajankohtana {{last_modified}}.\n"
"Jos haluat muuttaa antamiasi tietoja, voit mennä osoitteeseen {{url}}.\n"
@@ -269,11 +277,11 @@ msgstr ""
"Yhteyshenkilö:\n"
"{{contact}}"
-#: serviceform/emails.py:106
+#: emails.py:106
msgid ""
"Dear {{responsible}},\n"
"\n"
-"You can see all participants for the activities you are responsible of in "
+"You can see all participations for the activities you are responsible of in "
"the following URL:\n"
"{{url}}\n"
"\n"
@@ -297,9 +305,9 @@ msgstr ""
"Yhteyshenkilö: \n"
"{{contact}}"
-#: serviceform/emails.py:118
+#: emails.py:118
msgid ""
-"Dear {{participant}},\n"
+"Dear {{participation}},\n"
"\n"
"Your email address needs to be verified. Please do so by clicking link "
"below. Then you can\n"
@@ -313,7 +321,7 @@ msgid ""
"Contact person:\n"
"{{contact}}"
msgstr ""
-"Hei {{participant}},\n"
+"Hei {{participation}},\n"
"\n"
"Sähköpostiosoitteesi pitää varmentaa. Voit tehdä sen klikkaamalla alla "
"olevaa linkkiä. Sen jälkeen voit jatkaa\n"
@@ -327,94 +335,93 @@ msgstr ""
"Yhteyshenkilö:\n"
"{{contact}}"
-#: serviceform/forms.py:58
+#: forms.py:54
msgid "Revision"
msgstr "Versio"
-#: serviceform/forms.py:70
+#: forms.py:65
msgid "Current"
msgstr "Nykyinen"
-#: serviceform/forms.py:71
+#: forms.py:66
msgid "All"
msgstr "Kaikki"
-#: serviceform/forms.py:77
+#: forms.py:72
msgid "Save settings"
msgstr "Tallenna asetukset"
-#: serviceform/forms.py:91 serviceform/models/serviceform.py:206
+#: forms.py:86 models.py:525
msgid "Password"
msgstr "Salasana"
-#: serviceform/forms.py:102
+#: forms.py:93
msgid "Get in"
msgstr "Sisään!"
-#: serviceform/forms.py:107
+#: forms.py:98
msgid "Incorrect password"
msgstr "Virheellinen salasana"
-#: serviceform/forms.py:112 serviceform/forms.py:147
-#: serviceform/models/mixins.py:72 serviceform/models/mixins.py:80
-#: serviceform/templates/serviceform/participation/participation_form/snippets/responsible_contact_person.html:7
-#: serviceform/templates/serviceform/participation/participation_form/snippets/responsible_tooltip.html:10
+#: forms.py:103 forms.py:138 models.py:111 models.py:119
+#: templates/serviceform/participation/participation_form/snippets/responsible_contact_person.html:7
+#: templates/serviceform/participation/participation_form/snippets/responsible_tooltip.html:10
msgid "Email"
msgstr "Sähköposti"
-#: serviceform/forms.py:119 serviceform/forms.py:155
+#: forms.py:110 forms.py:145
msgid "Send the link!"
msgstr "Lähetä linkki!"
-#: serviceform/forms.py:130
+#: forms.py:121
msgid "There were no participation with email address {}"
msgstr ""
"Lomakkeelle ei löytynyt aikaisempaa osallistumistietoa sähköpostiosoitteella "
"{}"
-#: serviceform/forms.py:139 serviceform/forms.py:174
+#: forms.py:130 forms.py:164
msgid "Access link sent to email address {}"
msgstr "Linkki lähetetty sähköpostiosoitteeseen {}"
-#: serviceform/forms.py:141 serviceform/forms.py:176
+#: forms.py:132 forms.py:166
msgid "Email could not be sent to email address {}"
msgstr "Sähköpostia ei voitu lähettää osoitteeseen {}"
-#: serviceform/forms.py:165
+#: forms.py:155
msgid "There were no responsible with email address {}"
msgstr "Lomakkeelle ei löytynyt vastuuhenkilöä sähköpostiosoitteella {}"
-#: serviceform/forms.py:199
+#: forms.py:189
msgid "Continue"
msgstr "Jatka"
-#: serviceform/forms.py:202 serviceform/forms.py:280
+#: forms.py:192 forms.py:270
msgid "Save details"
msgstr "Tallenna tiedot"
-#: serviceform/forms.py:217
+#: forms.py:207
msgid "Optional"
msgstr "Vapaaehtoinen"
-#: serviceform/forms.py:236
+#: forms.py:226
msgid "Invalid year of birth"
msgstr "Virheellinen syntymävuosi"
-#: serviceform/forms.py:238
+#: forms.py:228
msgid "You must be at least 10 years old"
msgstr "Sinun pitää olla vähintään 10 vuotta vanha"
-#: serviceform/forms.py:246
+#: forms.py:236
msgid "If sending email is allowed email address need to be given"
msgstr ""
"Sähköpostiosoite pitää antaa, mikäli sähköpostin lähetys on asetettu "
"sallituksi"
-#: serviceform/forms.py:257
+#: forms.py:247
msgid "resend auth link to your email!"
msgstr "lähetä linkki sähköpostiisi!"
-#: serviceform/forms.py:259
+#: forms.py:249
msgid ""
"There is already participation with this email address. To edit earlier "
"participation, {}"
@@ -422,114 +429,83 @@ msgstr ""
"Lomakkeelle on jo annettu osallistujatieto tällä sähköpostiosoitteella. "
"Muokataksesi aikaisempia osallistumistietoja, {}"
-#: serviceform/forms.py:296
+#: forms.py:285
msgid "Yes I am sure"
msgstr "Olen varma"
-#: serviceform/forms.py:302
+#: forms.py:291
msgid "Delete"
msgstr "Poista"
-#: serviceform/forms.py:404 serviceform/forms.py:411
+#: forms.py:391 forms.py:398
msgid "Invalid activity input data"
msgstr "Virhe"
-#: serviceform/forms.py:414
+#: forms.py:401
msgid "Invalid input data in radio button"
msgstr "Virhe"
-#: serviceform/forms.py:418 serviceform/forms.py:426
+#: forms.py:405 forms.py:413
msgid "Invalid choice input data"
msgstr "Virhe"
-#: serviceform/forms.py:432
+#: forms.py:419
msgid "Invalid input data"
msgstr "Virhe"
-#: serviceform/forms.py:439
+#: forms.py:426
msgid "No choices selected!"
msgstr "Valitse yksi (tai useampi) vaihtoehdoista."
-#: serviceform/forms.py:519
+#: forms.py:505
msgid "Invalid question input data"
msgstr "Virhe"
-#: serviceform/forms.py:525
+#: forms.py:511
msgid "Answer required"
msgstr "Vastaus on pakollinen"
-#: serviceform/forms.py:535
+#: forms.py:521
msgid ""
-"Send invitations also to participants that have participated in older form "
+"Send invitations also to participations that have participated in older form "
"versions but not yet this form"
msgstr ""
"Lähetä kutsu myös niille henkilöille jotka ovat täyttäneet lomakkeen "
"aikaisemman version, mutta eivät vielä uusinta"
-#: serviceform/forms.py:538
+#: forms.py:524
msgid "Email addresses, separated by comma, space or enter"
msgstr ""
"Sähköpostiosoitteet, erotettuna pilkulla, välilyönnillä tai rivinvaihdolla"
-#: serviceform/forms.py:546
+#: forms.py:531
msgid "Send invites"
msgstr "Lähetä kutsut"
-#: serviceform/forms.py:555
+#: forms.py:540
msgid "Form is not yet published, emails can't be sent"
msgstr "Lomake ei ole avoinna tällä hetkellä, sähköposteja ei voi lähettää"
-#: serviceform/forms.py:562
+#: forms.py:547
msgid "Invalid email: {}"
msgstr "Virheellinen sähköpostiosoite: {}"
-#: serviceform/forms.py:574
+#: forms.py:559
msgid "Invitation sent to {}"
msgstr "Kutsu lähetetty osoitteeseen {}"
-#: serviceform/forms.py:577
+#: forms.py:562
msgid "Invitation was not sent to {} because user already exists"
msgstr ""
"Kutsua ei lähetetty osoitteeseen {} koska käyttäjä on jo järjestelmässä"
-#: serviceform/forms.py:580
+#: forms.py:565
msgid "Invitation was not sent to {} because user denied emailing"
msgstr ""
"Kutsua ei lähetetty osoitteeseen {} koska käyttäjä on kieltänyt sähköpostien "
"lähetyksen"
-#: serviceform/models/email.py:58 serviceform/models/email.py:115
-msgid "Content"
-msgstr "Sisältö"
-
-#: serviceform/models/email.py:63 serviceform/models/email.py:114
-msgid "Subject"
-msgstr "Otsikko"
-
-#: serviceform/models/email.py:107
-msgid "Email template"
-msgstr "Sähköpostimalli"
-
-#: serviceform/models/email.py:108
-msgid "Email templates"
-msgstr "Sähköpostimallit"
-
-#: serviceform/models/email.py:113
-msgid "Template name"
-msgstr "Mallin nimi"
-
-#: serviceform/models/email.py:116
-msgid ""
-"Following context may (depending on topic) be available for both subject and "
-"content: {{responsible}}, {{participant}}, {{last_modified}}, {{form}}, "
-"{{url}}, {{contact}}"
-msgstr ""
-"Seuraavat sisältömuuttujat ovat mahdollisesti (riippuen sähköpostin "
-"asiayhteydestä) käytettävissä sekä otsikkoon että sisältöön: "
-"{{responsible}}, {{participant}}, {{last_modified}}, {{form}}, {{url}}, "
-"{{contact}}"
-
-#: serviceform/models/mixins.py:40
+#: models.py:72
msgid ""
"Phone number must be entered in the format: '050123123' or '+35850123123'. "
"Up to 15 digits allowed."
@@ -537,71 +513,69 @@ msgstr ""
"Puhelinnumeron pitää olla muotoa: '050123123' tai '+35850123123', enintään "
"15 merkkiä pitkä."
-#: serviceform/models/mixins.py:45
+#: models.py:80
msgid "Enter a valid postal code."
msgstr "Syötä kunnollinen postinumero"
-#: serviceform/models/mixins.py:64
+#: models.py:103
msgid "Forename(s)"
msgstr "Etunimi"
-#: serviceform/models/mixins.py:65
+#: models.py:104
msgid "Surname"
msgstr "Sukunimi"
-#: serviceform/models/mixins.py:67 serviceform/models/serviceform.py:243
-#: serviceform/models/serviceform.py:247
+#: models.py:106 models.py:562 models.py:566
msgid "Street address"
msgstr "Katuosoite"
-#: serviceform/models/mixins.py:69
+#: models.py:108
msgid "Zip/Postal code"
msgstr "Postinumero"
-#: serviceform/models/mixins.py:71
+#: models.py:110
msgid "City"
msgstr "Kaupunki"
-#: serviceform/models/mixins.py:74 serviceform/models/mixins.py:82
-#: serviceform/models/serviceform.py:244 serviceform/models/serviceform.py:248
-#: serviceform/templates/serviceform/reports/snippets/_help.html:6
+#: models.py:113 models.py:121 models.py:563 models.py:567
+#: templates/serviceform/reports/snippets/_help.html:6
msgid "Phone number"
msgstr "Puhelinnumero"
-#: serviceform/models/mixins.py:78 serviceform/models/mixins.py:108
-#: serviceform/templates/serviceform/reports/snippets/_help.html:5
+#: models.py:117 models.py:144
+#: templates/serviceform/reports/snippets/_help.html:5
msgid "Name"
msgstr "Nimi"
-#: serviceform/models/mixins.py:84
-#: serviceform/templates/serviceform/participation/participation_form/snippets/responsible_contact_person.html:14
-#: serviceform/templates/serviceform/participation/participation_form/snippets/responsible_tooltip.html:13
-#: serviceform/templates/serviceform/reports/snippets/_help.html:13
+#: models.py:123
+#: templates/serviceform/participation/participation_form/snippets/responsible_contact_person.html:14
+#: templates/serviceform/participation/participation_form/snippets/responsible_tooltip.html:13
+#: templates/serviceform/reports/snippets/_help.html:13
msgid "Address"
msgstr "Osoite"
-#: serviceform/models/mixins.py:109 serviceform/models/serviceform.py:232
-#: serviceform/templates/serviceform/participation/participation_form/snippets/extra_info.html:6
+#: models.py:145 models.py:551
+#: templates/serviceform/participation/participation_form/snippets/extra_info.html:6
msgid "Description"
msgstr "Kuvaus"
-#: serviceform/models/mixins.py:161
+#: models.py:195
msgid "Secret key"
msgstr "Salainen avain"
-#: serviceform/models/people.py:43
+#: models.py:231
msgid "Responsibility person"
msgstr "Vastuuhenkilö"
-#: serviceform/models/people.py:44
+#: models.py:232
msgid "Responsibility persons"
msgstr "Vastuuhenkilöt"
-#: serviceform/models/people.py:52
+#: models.py:240
msgid "Send email notifications"
msgstr "Lähetä ilmoituksia sähköpostilla"
-#: serviceform/models/people.py:54
+#: models.py:242
msgid ""
"Send email notifications whenever new participation to administered "
"activities is registered. Email contains also has a link that allows "
@@ -612,172 +586,60 @@ msgstr ""
"linkin, jonka avulla on mahdollista tarkastella henkiön hallinnoimiin "
"tehtäviin osallistuneiden henkilöiden tietoja."
-#: serviceform/models/people.py:58
+#: models.py:246
msgid "Hide contact details in form"
msgstr "Piilota kaikki yhteystiedot lomakkeessa"
-#: serviceform/models/people.py:59
+#: models.py:247
msgid "Grant access to full reports"
msgstr "Myönnä pääsy kaikkiin raportteihin"
-#: serviceform/models/people.py:70 serviceform/models/people.py:232
+#: models.py:258 models.py:1087
msgid "Link to personal report"
msgstr "Linkki henkilökohtaiseen raporttiin"
-#: serviceform/models/people.py:117
-msgid "Participant"
-msgstr "Osallistuja"
-
-#: serviceform/models/people.py:118 serviceform/urls.py:89
-msgid "Participants"
-msgstr "Osallistujat"
-
-#: serviceform/models/people.py:145
-msgid "invited"
-msgstr "Kutsuttu"
-
-#: serviceform/models/people.py:146
-msgid "ongoing"
-msgstr "Meneillään"
-
-#: serviceform/models/people.py:147
-msgid "updating"
-msgstr "Päivittää tietoja"
-
-#: serviceform/models/people.py:148
-msgid "finished"
-msgstr "Valmis"
-
-#: serviceform/models/people.py:151 serviceform/models/people.py:181
-#: serviceform/models/serviceform.py:242 serviceform/models/serviceform.py:246
-msgid "Year of birth"
-msgstr "Syntymävuosi"
-
-#: serviceform/models/people.py:154 serviceform/models/serviceform.py:114
-msgid "Created at"
-msgstr "Luotu"
-
-#: serviceform/models/people.py:155 serviceform/models/people.py:187
-msgid "Last modified"
-msgstr "Viimeksi muokattu"
-
-#: serviceform/models/people.py:156 serviceform/models/people.py:186
-msgid "Last finished"
-msgstr "Viimeksi valmistunut"
-
-#: serviceform/models/people.py:161
-msgid "Email verified"
-msgstr "Sähköpostiosoite varmennettu"
-
-#: serviceform/models/people.py:163
-msgid "Sending email allowed"
-msgstr "Sähköpostin lähetys sallittu"
-
-#: serviceform/models/people.py:164
-msgid ""
-"You will receive email that contains a link that allows later modification "
-"of the form. Also when new version of form is published, you will be "
-"notified. It is highly recommended that you keep this enabled unless you "
-"move away and do not want to participate at all any more. You can also "
-"change this setting later if you wish."
-msgstr ""
-"Saat sähköpostia joka sisältää linkin jonka avulla voit muuttaa tietoja "
-"myöhemmin. Tämän lisäksi uuden lomakkeen version julkaisemisen yhteydessä "
-"sinulle lähetetään sähköpostia. Suosittelemme, että sallit sähköpostituksen, "
-"paitsi siinä tapauksessa jos olet muuttanut pois etkä halua osallistua enää "
-"lainkaan. Voit myös myöhemmin muuttaa tätä asetusta."
-
-#: serviceform/models/people.py:185
-msgid "Participant created in system"
-msgstr "Osallistuja talletettu ensimmäisen kerran järjestelmään"
-
-#: serviceform/models/people.py:188
-msgid "Email address verified"
-msgstr "Sähköpostiosoite varmennettu"
-
-#: serviceform/models/people.py:188 serviceform/models/people.py:189
-#: serviceform/templates/serviceform/participation/preview_view.html:89
-#: serviceform/templates/serviceform/reports/contents/_all_questions.html:18
-#: serviceform/templates/serviceform/reports/contents/_responsible_contents.html:54
-#: serviceform/templates/serviceform/reports/view_participant.html:76
-msgid "No"
-msgstr "Ei"
-
-#: serviceform/models/people.py:188 serviceform/models/people.py:189
-#: serviceform/templates/serviceform/participation/preview_view.html:87
-#: serviceform/templates/serviceform/reports/contents/_all_questions.html:16
-#: serviceform/templates/serviceform/reports/contents/_responsible_contents.html:52
-#: serviceform/templates/serviceform/reports/view_participant.html:74
-msgid "Yes"
-msgstr "Kyllä"
-
-#: serviceform/models/people.py:189
-msgid "Emails allowed"
-msgstr "Sähköpostin lähetys sallittu"
-
-#: serviceform/models/people.py:190
-msgid "Form status"
-msgstr "Lomakkeen tila"
-
-#: serviceform/models/people.py:216 serviceform/models/serviceform.py:541
-msgid "Activities"
-msgstr "Tehtävät"
-
-#: serviceform/models/people.py:225
-msgid "Form"
-msgstr "Lomake"
-
-#: serviceform/models/people.py:366
-msgid ""
-"Updated information has been stored! Please proceed until the end of the "
-"form."
-msgstr ""
-"Päivitetyt tiedot on talletettu järjestelmään! Etenethän kuitenkin lomakkeen "
-"loppuun saakka."
-
-#: serviceform/models/serviceform.py:71
+#: models.py:303
msgid "Form revision"
msgstr "Lomakkeen versio"
-#: serviceform/models/serviceform.py:72
+#: models.py:304
msgid "Form revisions"
msgstr "Lomakkeen versiot"
-#: serviceform/models/serviceform.py:76
+#: models.py:308
msgid "Revision name"
msgstr "Version nimi"
-#: serviceform/models/serviceform.py:77 serviceform/models/serviceform.py:102
-#: serviceform/templates/serviceform/base.html:12
+#: models.py:309 models.py:421 templates/serviceform/base.html:12
msgid "Service form"
msgstr "Palvelulomake"
-#: serviceform/models/serviceform.py:78
+#: models.py:310
msgid "Valid from"
msgstr "Voimassa alkaen"
-#: serviceform/models/serviceform.py:80
+#: models.py:312
msgid "Valid to"
msgstr "Voimassaolo päättyy"
-#: serviceform/models/serviceform.py:83
-msgid "Send bulk email to participants"
+#: models.py:315
+msgid "Send bulk email to participations"
msgstr "Lähetä sähköpostia osallistujille"
-#: serviceform/models/serviceform.py:84
+#: models.py:316
msgid ""
-"Send email to participants that filled the form when this revision was "
+"Send email to participations that filled the form when this revision was "
"active. Email is sent when new current revision is published."
msgstr ""
"Lähetä sähköpostia niille sallistujille jotka täyttivät lomakkeen silloin "
"kun tämä versio oli aktiivinen. Sähköpostia lähetetään kun uusi versio "
"lomakkeesta julkaistaan."
-#: serviceform/models/serviceform.py:88
+#: models.py:320
msgid "Email sending starts"
msgstr "Sähköpostin lähetys alkaa"
-#: serviceform/models/serviceform.py:90
+#: models.py:322
msgid ""
"Sends bulk email to responsibility persons at specified time, after which it "
"will send email for each new participation"
@@ -786,138 +648,173 @@ msgstr ""
"sähköpostia lähetetään aina kun uusi ilmoittautuminen rekisteröityy "
"järjestelmään."
-#: serviceform/models/serviceform.py:103
+#: models.py:351 models.py:407
+msgid "Content"
+msgstr "Sisältö"
+
+#: models.py:356 models.py:406
+msgid "Subject"
+msgstr "Otsikko"
+
+#: models.py:399
+msgid "Email template"
+msgstr "Sähköpostimalli"
+
+#: models.py:400
+msgid "Email templates"
+msgstr "Sähköpostimallit"
+
+#: models.py:405
+msgid "Template name"
+msgstr "Mallin nimi"
+
+#: models.py:408
+msgid ""
+"Following context may (depending on topic) be available for both subject and "
+"content: {{responsible}}, {{participation}}, {{last_modified}}, {{form}}, "
+"{{url}}, {{contact}}"
+msgstr ""
+"Seuraavat sisältömuuttujat ovat mahdollisesti (riippuen sähköpostin "
+"asiayhteydestä) käytettävissä sekä otsikkoon että sisältöön: "
+"{{responsible}}, {{participation}}, {{last_modified}}, {{form}}, {{url}}, "
+"{{contact}}"
+
+#: models.py:422
msgid "Service forms"
msgstr "Palvelulomakkeet"
-#: serviceform/models/serviceform.py:109
+#: models.py:428
msgid "Name of the serviceform"
msgstr "Palvelulomakkeen nimi"
-#: serviceform/models/serviceform.py:110
+#: models.py:429
msgid "Slug"
msgstr "Nimi URLissa"
-#: serviceform/models/serviceform.py:111
+#: models.py:430
msgid "This is part of the form url, i.e. form will be located {}/yourslug"
msgstr ""
"Tämä on osa lomakkeen osoitetta, ts. lomakkeen osoitteeksi tulee {}/"
"valitsemasi-nimi-urlissa"
-#: serviceform/models/serviceform.py:115
+#: models.py:433 models.py:1010
+msgid "Created at"
+msgstr "Luotu"
+
+#: models.py:434
msgid "Last updated"
msgstr "Viimeksi päivitetty"
-#: serviceform/models/serviceform.py:116
+#: models.py:435
msgid "Last edited by"
msgstr "Viimeisin muokkaaja"
-#: serviceform/models/serviceform.py:122 serviceform/models/serviceform.py:280
+#: models.py:441 models.py:599
msgid "Responsible"
msgstr "Vastuuhenkilö"
-#: serviceform/models/serviceform.py:125
+#: models.py:444
msgid "Require email verification"
msgstr "Vaadi sähköpostiosoitteen varmentamista"
-#: serviceform/models/serviceform.py:130
-msgid "Verification email to participant"
+#: models.py:449
+msgid "Verification email to participation"
msgstr "Sähköpostin varmennusviesti osallistujalle"
-#: serviceform/models/serviceform.py:132
+#: models.py:451
msgid ""
-"Email verification message that is sent to participant when filling form, if "
+"Email verification message that is sent to participation when filling form, if "
"email verification is enabled"
msgstr ""
"Sähköpostin varmennusviesti joka lähetetään osallistujalle kun tämä täyttää "
"lomaketta, mikäli sähköpostin varmennus vaaditaan."
-#: serviceform/models/serviceform.py:139
+#: models.py:458
msgid "Email to responsibles"
msgstr "Sähköposti vastuullisille"
-#: serviceform/models/serviceform.py:140
+#: models.py:459
msgid "Email that is sent to responsibles when new participation is registered"
msgstr ""
"Sähköposti, joka lähetetään vastuullisille kun uusi osallistujatieto on "
"rekisteröity"
-#: serviceform/models/serviceform.py:145
+#: models.py:464
msgid "Bulk email to responsibles"
msgstr "Massasähköposti vastuullisille"
-#: serviceform/models/serviceform.py:146
+#: models.py:465
msgid "Email that is sent to responsibles when emailing starts"
msgstr ""
"Sähköposti, joka lähetetään vastuullisille, kun vastuullisten sähköpostitus "
"aloitetaan"
-#: serviceform/models/serviceform.py:153
+#: models.py:472
msgid "Responsible requests auth link"
msgstr "Vastuussa oleva yhteyshenkilö pyytää linkkiä raporttiin"
-#: serviceform/models/serviceform.py:155
+#: models.py:474
msgid "Email that is sent to responsible when he requests auth link"
msgstr ""
"Sähköposti, joka lähetetään vastuullisille, kun vastuullinen pyytää "
"raporttilinkkiä"
-#: serviceform/models/serviceform.py:164
-msgid "Email to participant, on finish"
+#: models.py:483
+msgid "Email to participation, on finish"
msgstr "Sähköposti osallistujalle, valmis lomake"
-#: serviceform/models/serviceform.py:165
+#: models.py:484
msgid ""
-"Email that is sent to participant after he has fulfilled his participation"
+"Email that is sent to participation after he has fulfilled his participation"
msgstr ""
"Sähköposti joka lähetetään osallistujalle sen jälkeen kun hän on jättänyt "
"lomakkeen järjestelmään"
-#: serviceform/models/serviceform.py:170
-msgid "Email to participant, on update"
+#: models.py:489
+msgid "Email to participation, on update"
msgstr "Sähköposti osallistujalle, päivitetty lomake"
-#: serviceform/models/serviceform.py:171
+#: models.py:490
msgid ""
-"Email that is sent to participant after he has updated his participation"
+"Email that is sent to participation after he has updated his participation"
msgstr ""
"Sähköposti joka lähetetään osallistujalle sen jälkeen kun hän on päivittänyt "
"tietojansa"
-#: serviceform/models/serviceform.py:177
-msgid "Resend email to participant"
+#: models.py:496
+msgid "Resend email to participation"
msgstr "Sähköposti vastaanottajalle, uudelleenlähetys"
-#: serviceform/models/serviceform.py:178
-msgid "Email that is sent to participant if he requests resending email"
+#: models.py:497
+msgid "Email that is sent to participation if he requests resending email"
msgstr ""
"Sähköposti, joka lähetetään osallistujalle jos hän pyytää sähköpostilinkin "
"uudelleenlähetystä"
-#: serviceform/models/serviceform.py:184
-msgid "Bulk email to former participants"
+#: models.py:503
+msgid "Bulk email to former participations"
msgstr "Massasähköpostia aikaisemmille osallistujille"
-#: serviceform/models/serviceform.py:185
-msgid "Email that is sent to former participants when form is published"
+#: models.py:504
+msgid "Email that is sent to former participations when form is published"
msgstr ""
"Sähköposti joka lähetetään aikaisemmille osallistujille silloin kun lomake "
"julkistetaan"
-#: serviceform/models/serviceform.py:191
+#: models.py:510
msgid "Invite email"
msgstr "Kutsusähköposti"
-#: serviceform/models/serviceform.py:193
+#: models.py:512
msgid ""
"Email that is sent when user is invited to the form manually via invite form"
msgstr "Sähköposti joka lähetetään kun käyttäjä on kutsuttu järjestelmään"
-#: serviceform/models/serviceform.py:199
+#: models.py:518
msgid "Current revision"
msgstr "Käytössä oleva versio"
-#: serviceform/models/serviceform.py:201
+#: models.py:520
msgid ""
"You need to first add a revision to form (see below) and save. Then newly "
"created revision will appear in the list."
@@ -925,28 +822,28 @@ msgstr ""
"Lisää ensin lomakkeeseen versio (katso alla) ja tallenna. Sen jälkeen uusi "
"versio ilmestyy listaan."
-#: serviceform/models/serviceform.py:207
-msgid "Password that is asked from participants"
+#: models.py:526
+msgid "Password that is asked from participations"
msgstr "Salasana kysellään lomakkeen täyttäjiltä"
-#: serviceform/models/serviceform.py:211
+#: models.py:530
msgid "Hide contact details (other than email) in form"
msgstr "Piilota yhteystiedot (muut paitsi sähköposti) lomakkeessa"
-#: serviceform/models/serviceform.py:212
+#: models.py:531
msgid "Split participation form to level 1 categories"
msgstr "Jaa osallistumislomake tason 1 kategorioiden mukaisesti osiin"
-#: serviceform/models/serviceform.py:214
+#: models.py:533
msgid "Please note that preview shows full form despite this option"
msgstr ""
"Huomaa että esikatselu näyttää koko lomakkeen riippumatta tästä valinnasta"
-#: serviceform/models/serviceform.py:216
+#: models.py:535
msgid "Allow jumping between categories"
msgstr "Salli hyppiminen kategorioiden välillä"
-#: serviceform/models/serviceform.py:218
+#: models.py:537
msgid ""
"In effect only if flow by categories option is enabled. If this option is "
"enabled, user can jump between categories. If disabled, he must proceed them "
@@ -956,45 +853,44 @@ msgstr ""
"asetus on päällä, käyttäjä voi hyppiä kategorioiden välillä. Jos asetus on "
"poissa päältä, käyttäjän pitää käydä läpi kukin kategoria yksi kerrallaan."
-#: serviceform/models/serviceform.py:222
+#: models.py:541
msgid "Level 1 category default background color"
msgstr "Tason 1 kategorian oletusarvoinen taustaväri"
-#: serviceform/models/serviceform.py:224
+#: models.py:543
msgid "If left blank (black), default coloring will be used"
msgstr "Jos tämä jätetään tyhjäksi (musta), käytetään oletusvärejä"
-#: serviceform/models/serviceform.py:225
+#: models.py:544
msgid "Level 2 category default background color"
msgstr "Tason 2 kategorian oletusarvoinen taustaväri"
-#: serviceform/models/serviceform.py:227
+#: models.py:546
msgid "If left blank (black), it will be derived from level 1 background color"
msgstr ""
"Jos jätetään tyhjäksi (musta), väri johdetaan tason 1 kategorian "
"taustaväristä"
-#: serviceform/models/serviceform.py:228
+#: models.py:547
msgid "Activity default background color"
msgstr "Aktiviteetin oletusarvoinen taustaväri"
-#: serviceform/models/serviceform.py:229
+#: models.py:548
msgid "If left blank (black), it will be derived from level 2 background color"
msgstr ""
"Jos jätetään tyhjäksi (musta), väri johdetaan tason 2 kategorian "
"taustaväristä"
-#: serviceform/models/serviceform.py:233
+#: models.py:552
msgid ""
"Description box will be shown before instruction box in participation view."
msgstr "Kuvaus näytetään osallistumisnäkymässä ennen lomakkeen täyttöohjeita"
-#: serviceform/models/serviceform.py:235
-#: serviceform/templates/serviceform/participation/participation_view.html:16
+#: models.py:554 templates/serviceform/participation/participation_view.html:16
msgid "Instructions"
msgstr "Ohjeet"
-#: serviceform/models/serviceform.py:236
+#: models.py:555
msgid ""
"Use HTML formatting. Leave this empty to use default. This is shown in "
"participation view."
@@ -1003,262 +899,362 @@ msgstr ""
"määrittelet ohjeen tässä, käytä HTML-muotoilua. Ohjeet näytetään "
"osallistumisnäkymässä."
-#: serviceform/models/serviceform.py:239
+#: models.py:558
msgid "Login text"
msgstr "Teksti kirjautumisnäkymässä"
-#: serviceform/models/serviceform.py:240
+#: models.py:559
msgid "This will be shown in the login screen"
msgstr "Tämä näytetään kirjautumisnäkymässä"
-#: serviceform/models/serviceform.py:260
+#: models.py:561 models.py:565 models.py:1007 models.py:1037
+msgid "Year of birth"
+msgstr "Syntymävuosi"
+
+#: models.py:579
msgid "Can access"
msgstr "Pystyy muokkaamaan lomaketta"
-#: serviceform/models/serviceform.py:279
+#: models.py:598
msgid "Default"
msgstr "Oletus"
-#: serviceform/models/serviceform.py:281
+#: models.py:600
msgid "defaultresponsible@email.com"
msgstr "oletusvastuullinen@email.com"
-#: serviceform/models/serviceform.py:295
+#: models.py:614
msgid "Default bulk email to responsibles"
msgstr "Massasähköposti vastuullisille (oletus)"
-#: serviceform/models/serviceform.py:297
+#: models.py:616
msgid "Participations can be now viewed for form {{form}}"
msgstr "Osallistujatiedot lomakkeelle {{form}} ovat nyt tarkasteltavissa"
-#: serviceform/models/serviceform.py:301
+#: models.py:620
msgid "Default email to responsibles"
msgstr "Sähköposti vastuullisille (oletus)"
-#: serviceform/models/serviceform.py:303
+#: models.py:622
msgid "New participation arrived for form {{form}}"
msgstr "Uusi osallistujatieto lähetetty lomakkeelle {{form}}"
-#: serviceform/models/serviceform.py:307
-msgid "Default email to participant, on finish"
+#: models.py:626
+msgid "Default email to participation, on finish"
msgstr "Osallistujalle: valmis lomake (oletus)"
-#: serviceform/models/serviceform.py:309
+#: models.py:628
msgid "Your update to form {{form}}"
msgstr "Päivityksesi lomakkeelle {{form}}"
-#: serviceform/models/serviceform.py:313
-msgid "Default email to participant, on update"
+#: models.py:632
+msgid "Default email to participation, on update"
msgstr "Osallistujalle: päivitetty lomake (oletus)"
-#: serviceform/models/serviceform.py:315
+#: models.py:634
msgid "Your updated participation to form {{form}}"
msgstr "Päivittämäsi osallistujatietosi lomakkeelle {{form}}"
-#: serviceform/models/serviceform.py:319
-msgid "Default email to former participants"
+#: models.py:638
+msgid "Default email to former participations"
msgstr "Sähköposti aikaisemmille osallistujille (oletus)"
-#: serviceform/models/serviceform.py:321
+#: models.py:640
msgid "New form revision to form {{form}} has been published"
msgstr "Uusi versio lomakkeesta {{form}} on julkaistu"
-#: serviceform/models/serviceform.py:325
-msgid "Default resend email to participant"
+#: models.py:644
+msgid "Default resend email to participation"
msgstr "Sähköposti vastaanottajalle, uudelleenlähetys (oletus)"
-#: serviceform/models/serviceform.py:327
+#: models.py:646
msgid "Your participation to form {{form}}"
msgstr "Osallistujatietosi lomakkeelle {{form}}"
-#: serviceform/models/serviceform.py:331
-msgid "Default invite email to participants"
+#: models.py:650
+msgid "Default invite email to participations"
msgstr "Kutsusähköposti osallistujille (oletus)"
-#: serviceform/models/serviceform.py:333
+#: models.py:652
msgid "Invitation to fill participation in {{form}}"
msgstr "Kutsu täyttää osallistumistiedot lomakkeelle {{form}}"
-#: serviceform/models/serviceform.py:337
+#: models.py:656
msgid "Default request responsible auth link email"
msgstr "Sähköposti kun vastuuhenkilö pyytää raporttilinkkiä (oletus)"
-#: serviceform/models/serviceform.py:339
+#: models.py:658
msgid "Your report in {{form}}"
msgstr "Raporttisi lomakkeelle {{form}}"
-#: serviceform/models/serviceform.py:343
-msgid "Default verification email to participant"
+#: models.py:662
+msgid "Default verification email to participation"
msgstr "Sähköpostiosoitteen varmennusviesti osallistujalle (oletus)"
-#: serviceform/models/serviceform.py:345
+#: models.py:664
msgid "Please verify your email in {{form}}"
msgstr "Sinun pitää varmentaa sähköpostiosoiteesi lomakkeessa {{form}}"
-#: serviceform/models/serviceform.py:392
+#: models.py:711
msgid "Is open?"
msgstr "Auki?"
-#: serviceform/models/serviceform.py:412
+#: models.py:734
msgid "To report"
msgstr "Raporttiin"
-#: serviceform/models/serviceform.py:414
+#: models.py:736
msgid "To form"
msgstr "Lomakkeeseen"
-#: serviceform/models/serviceform.py:416 serviceform/urls.py:80
+#: models.py:738 urls.py:81
msgid "Preview"
msgstr "Esikatselu"
-#: serviceform/models/serviceform.py:418
+#: models.py:740
msgid "Printable"
msgstr "Tulostettava"
-#: serviceform/models/serviceform.py:420 serviceform/urls.py:102
+#: models.py:742 urls.py:103
msgid "Invite"
msgstr "Kutsu"
-#: serviceform/models/serviceform.py:423
+#: models.py:745
msgid "Links"
msgstr "Linkit"
-#: serviceform/models/serviceform.py:438
+#: models.py:760
msgid "Participation count"
msgstr "Osallistujien määrä"
-#: serviceform/models/serviceform.py:480
+#: models.py:802
msgid "Order"
msgstr "Järjestysnumero"
-#: serviceform/models/serviceform.py:482
+#: models.py:804
msgid "Responsible persons"
msgstr "Vastuuhenkilöt"
-#: serviceform/models/serviceform.py:484
+#: models.py:806
msgid "Choose responsibles"
msgstr "Valitse vastuuhenkilö(t)"
-#: serviceform/models/serviceform.py:494
+#: models.py:816
msgid "{} (and others)"
msgstr "{} (ja muita)"
-#: serviceform/models/serviceform.py:504 serviceform/models/serviceform.py:519
+#: models.py:823 models.py:838
msgid "Background color"
msgstr "Taustaväri"
-#: serviceform/models/serviceform.py:507 serviceform/models/serviceform.py:525
+#: models.py:826 models.py:844
msgid "Level 1 category"
msgstr "Tason 1 kategoria"
-#: serviceform/models/serviceform.py:508
+#: models.py:827
msgid "Level 1 categories"
msgstr "Tason 1 kategoriat"
-#: serviceform/models/serviceform.py:522
+#: models.py:841
msgid "Level 2 category"
msgstr "Tason 2 kategoria"
-#: serviceform/models/serviceform.py:523
+#: models.py:842
msgid "Level 2 categories"
msgstr "Tason 2 kategoriat"
-#: serviceform/models/serviceform.py:540
-#: serviceform/templates/serviceform/participation/preview_view.html:33
-#: serviceform/templates/serviceform/reports/view_participant.html:25
+#: models.py:859 templates/serviceform/participation/preview_view.html:33
+#: templates/serviceform/reports/view_participation.html:25
msgid "Activity"
msgstr "Tehtävä"
-#: serviceform/models/serviceform.py:543
+#: models.py:860 models.py:1071
+msgid "Activities"
+msgstr "Tehtävät"
+
+#: models.py:862
msgid "Category"
msgstr "Luokittelu"
-#: serviceform/models/serviceform.py:545
+#: models.py:864
msgid "Multichoice"
msgstr "Monivalinta"
-#: serviceform/models/serviceform.py:546 serviceform/models/serviceform.py:588
+#: models.py:865 models.py:900
msgid "Needed"
msgstr "Tarve"
-#: serviceform/models/serviceform.py:547 serviceform/models/serviceform.py:589
+#: models.py:866 models.py:901
msgid "Skip"
msgstr "Ylim."
-#: serviceform/models/serviceform.py:584
+#: models.py:896
msgid "Activity choice"
msgstr "Vaihtoehto"
-#: serviceform/models/serviceform.py:585
+#: models.py:897
msgid "Activity choices"
msgstr "Vaihtoehdot"
-#: serviceform/models/serviceform.py:622 serviceform/models/serviceform.py:639
-#: serviceform/templates/serviceform/participation/preview_view.html:73
-#: serviceform/templates/serviceform/reports/view_participant.html:60
+#: models.py:930 models.py:947
+#: templates/serviceform/participation/preview_view.html:73
+#: templates/serviceform/reports/view_participation.html:60
msgid "Question"
msgstr "Kysymys"
-#: serviceform/models/serviceform.py:623 serviceform/urls.py:78
+#: models.py:931 urls.py:79
msgid "Questions"
msgstr "Kysymykset"
-#: serviceform/models/serviceform.py:631
+#: models.py:939
msgid "Integer"
msgstr "Kokonaisluku"
-#: serviceform/models/serviceform.py:632
+#: models.py:940
msgid "Short text"
msgstr "Lyhyt teksti"
-#: serviceform/models/serviceform.py:633
+#: models.py:941
msgid "Long text"
msgstr "Pitkä teksti"
-#: serviceform/models/serviceform.py:634
+#: models.py:942
msgid "Boolean"
msgstr "Kyllä / ei"
-#: serviceform/models/serviceform.py:635
+#: models.py:943
msgid "Date"
msgstr "Päivämäärä"
-#: serviceform/models/serviceform.py:641
+#: models.py:949
msgid "Answer type"
msgstr "Vastauksen tyyppi"
-#: serviceform/models/serviceform.py:642
+#: models.py:950
msgid "Answer required?"
msgstr "Vaaditaanko vastaus?"
-#: serviceform/templates/serviceform/base.html:50
-msgid "Serviceform code is available at GitHub"
-msgstr "Palvelulomakkeen lähdekoodit ovat saatavana GitHubissa"
+#: models.py:973
+msgid "Participation"
+msgstr "Osallistuja"
+
+#: models.py:974 urls.py:90
+msgid "Participations"
+msgstr "Osallistujat"
+
+#: models.py:1001
+msgid "invited"
+msgstr "Kutsuttu"
+
+#: models.py:1002
+msgid "ongoing"
+msgstr "Meneillään"
+
+#: models.py:1003
+msgid "updating"
+msgstr "Päivittää tietoja"
+
+#: models.py:1004
+msgid "finished"
+msgstr "Valmis"
+
+#: models.py:1011 models.py:1043
+msgid "Last modified"
+msgstr "Viimeksi muokattu"
+
+#: models.py:1012 models.py:1042
+msgid "Last finished"
+msgstr "Viimeksi valmistunut"
+
+#: models.py:1017
+msgid "Email verified"
+msgstr "Sähköpostiosoite varmennettu"
+
+#: models.py:1019
+msgid "Sending email allowed"
+msgstr "Sähköpostin lähetys sallittu"
+
+#: models.py:1020
+msgid ""
+"You will receive email that contains a link that allows later modification "
+"of the form. Also when new version of form is published, you will be "
+"notified. It is highly recommended that you keep this enabled unless you "
+"move away and do not want to participate at all any more. You can also "
+"change this setting later if you wish."
+msgstr ""
+"Saat sähköpostia joka sisältää linkin jonka avulla voit muuttaa tietoja "
+"myöhemmin. Tämän lisäksi uuden lomakkeen version julkaisemisen yhteydessä "
+"sinulle lähetetään sähköpostia. Suosittelemme, että sallit sähköpostituksen, "
+"paitsi siinä tapauksessa jos olet muuttanut pois etkä halua osallistua enää "
+"lainkaan. Voit myös myöhemmin muuttaa tätä asetusta."
+
+#: models.py:1041
+msgid "Participation created in system"
+msgstr "Osallistuja talletettu ensimmäisen kerran järjestelmään"
+
+#: models.py:1044
+msgid "Email address verified"
+msgstr "Sähköpostiosoite varmennettu"
+
+#: models.py:1044 models.py:1045
+#: templates/serviceform/participation/preview_view.html:89
+#: templates/serviceform/reports/contents/_all_questions.html:13
+#: templates/serviceform/reports/contents/_responsible_contents.html:62
+#: templates/serviceform/reports/view_participation.html:76
+msgid "No"
+msgstr "Ei"
-#: serviceform/templates/serviceform/error/404.html:5
+#: models.py:1044 models.py:1045
+#: templates/serviceform/participation/preview_view.html:87
+#: templates/serviceform/reports/contents/_all_questions.html:11
+#: templates/serviceform/reports/contents/_responsible_contents.html:60
+#: templates/serviceform/reports/view_participation.html:74
+msgid "Yes"
+msgstr "Kyllä"
+
+#: models.py:1045
+msgid "Emails allowed"
+msgstr "Sähköpostin lähetys sallittu"
+
+#: models.py:1046
+msgid "Form status"
+msgstr "Lomakkeen tila"
+
+#: models.py:1080
+msgid "Form"
+msgstr "Lomake"
+
+#: models.py:1221
+msgid ""
+"Updated information has been stored! Please proceed until the end of the "
+"form."
+msgstr ""
+"Päivitetyt tiedot on talletettu järjestelmään! Etenethän kuitenkin lomakkeen "
+"loppuun saakka."
+
+#: templates/serviceform/error/404.html:5
msgid "Sorry, page was not found. "
msgstr "Anteeksi, sivua ei löytynyt"
-#: serviceform/templates/serviceform/login/_login_navbar.html:12
-#: serviceform/urls.py:107
+#: templates/serviceform/login/_login_navbar.html:12 urls.py:108
msgid "Admin"
msgstr "Ylläpito"
-#: serviceform/templates/serviceform/login/_login_navbar.html:18
-#: serviceform/templates/serviceform/navbar/navbar_base.html:28
-#: serviceform/templates/serviceform/navbar/navbar_base.html:41
-#: serviceform/templates/serviceform/reports/_report_navbar.html:11
+#: templates/serviceform/login/_login_navbar.html:18
+#: templates/serviceform/navbar/navbar_base.html:28
+#: templates/serviceform/navbar/navbar_base.html:41
+#: templates/serviceform/reports/_report_navbar.html:11
msgid "current"
msgstr "nykyinen"
-#: serviceform/templates/serviceform/login/password_login.html:5
+#: templates/serviceform/login/password_login.html:4
msgid ""
"If this is the first time you fill this form, you need a password to continue"
msgstr ""
"Jos tämä on ensimmäinen kerta kun täytät tämän lomakkeen, sinun pitää antaa "
"salasana jatkaaksesi"
-#: serviceform/templates/serviceform/login/send_participant_auth_link.html:5
+#: templates/serviceform/login/send_participation_auth_link.html:5
msgid ""
"If you have filled this form already in the past, please give your email\n"
" below so we can send you a personal link to update your participation "
@@ -1269,7 +1265,7 @@ msgstr ""
"niin lähetämme sinulle henkilökohtaisen linkin, jonka kautta voit päivittää "
"osallistumistietojasi."
-#: serviceform/templates/serviceform/login/send_responsible_auth_link.html:5
+#: templates/serviceform/login/send_responsible_auth_link.html:5
msgid ""
"If you are marked as a responsible person, please give your email below so\n"
" we can send you a personal link to view your participation report."
@@ -1278,36 +1274,36 @@ msgstr ""
"sähköpostiosoitteesi, niin lähetämme sinulle henkilökohtaisen linkin, jolla "
"voit tarkastella raporttiasi."
-#: serviceform/templates/serviceform/login/unsubscribe_participant.html:4
-msgid "You won't be getting any emails (as participant for "
+#: templates/serviceform/login/unsubscribe_participation.html:4
+msgid "You won't be getting any emails (as participation for "
msgstr "Et saa enää sähköposteja (osallistujana lomakkeelle "
-#: serviceform/templates/serviceform/login/unsubscribe_participant.html:4
-#: serviceform/templates/serviceform/login/unsubscribe_responsible.html:4
+#: templates/serviceform/login/unsubscribe_participation.html:4
+#: templates/serviceform/login/unsubscribe_member.html:4
msgid ") from this system any more."
msgstr ") tästä järjestelmästä tämän jälkeen."
-#: serviceform/templates/serviceform/login/unsubscribe_responsible.html:4
+#: templates/serviceform/login/unsubscribe_member.html:4
msgid "You won't be getting any emails (as responsible for "
msgstr "Et saa enää sähköposteja (vastuuhenkilönä lomakkeelle "
-#: serviceform/templates/serviceform/main_page.html:4
+#: templates/serviceform/main_page.html:4
msgid "Serviceform root. Nothing here"
msgstr "Palveluhakemuslomakkeen juuurihakemisto. Ei mitään täällä."
-#: serviceform/templates/serviceform/navbar/navbar_base.html:10
+#: templates/serviceform/navbar/navbar_base.html:10
msgid "Toggle navigation"
msgstr "Näytä navigointi"
-#: serviceform/templates/serviceform/participation/contact_view.html:7
+#: templates/serviceform/participation/contact_view.html:7
msgid "You can delete your participation from the system by clicking"
msgstr "Voit poistaa osallistumistietosi järjestelmästä painamalla"
-#: serviceform/templates/serviceform/participation/contact_view.html:7
+#: templates/serviceform/participation/contact_view.html:7
msgid "here"
msgstr "tästä"
-#: serviceform/templates/serviceform/participation/contact_view.html:8
+#: templates/serviceform/participation/contact_view.html:8
msgid ""
"Otherwise, please check if the information is correct and update it if "
"necessary. "
@@ -1315,22 +1311,22 @@ msgstr ""
"Muussa tapauksessa, tarkista että tiedot ovat oikein ja päivitä niitä "
"tarvittaessa."
-#: serviceform/templates/serviceform/participation/contact_view.html:10
+#: templates/serviceform/participation/contact_view.html:10
msgid "Please fill in your contact details"
msgstr "Syötä seuraavaksi yhteystietosi"
-#: serviceform/templates/serviceform/participation/delete_participation.html:6
+#: templates/serviceform/participation/delete_participation.html:6
msgid ""
"Are you sure you want to delete your participation permanently from the "
"system?"
msgstr ""
"Haluatko varmasti poistaa osallistumistietosi lopullisesti järjestelmästä?"
-#: serviceform/templates/serviceform/participation/email_verification.html:6
+#: templates/serviceform/participation/email_verification.html:6
msgid "Email sent to"
msgstr "Sähköpostia lähetettiin osoitteeseen"
-#: serviceform/templates/serviceform/participation/email_verification.html:7
+#: templates/serviceform/participation/email_verification.html:7
msgid ""
"Please verify your email address by clicking the link that is given in "
"email. Then you can continue filling the form."
@@ -1338,37 +1334,36 @@ msgstr ""
"Varmenna sähköpostiosoitteesi klikkaamalla sähköpostissa annettavaa linkkiä. "
"Sen jälkeen voit jatkaa lomakkeen täyttämistä."
-#: serviceform/templates/serviceform/participation/login_base.html:12
+#: templates/serviceform/participation/login_base.html:12
msgid "Form is not public at the moment."
msgstr "Lomake ei ole avoinna tällä hetkellä"
-#: serviceform/templates/serviceform/participation/participation_form/participation_form.html:6
-#: serviceform/templates/serviceform/participation/question_form/question_form.html:5
+#: templates/serviceform/participation/participation_form/participation_form.html:6
+#: templates/serviceform/participation/question_form/question_form.html:5
msgid "Please fix errors below"
msgstr "Korjaa alla olevat virheet"
-#: serviceform/templates/serviceform/participation/participation_form/snippets/category.html:25
-#: serviceform/templates/serviceform/participation/participation_form/snippets/extra_info.html:14
+#: templates/serviceform/participation/participation_form/snippets/category.html:27
+#: templates/serviceform/participation/participation_form/snippets/extra_info.html:14
msgid "Responsible contact person(s)"
msgstr "Vastuussa olevat yhteyshenkilö(t)"
-#: serviceform/templates/serviceform/participation/participation_form/snippets/extra_info.html:20
+#: templates/serviceform/participation/participation_form/snippets/extra_info.html:20
msgid "Please give additional information if needed"
msgstr "Anna tähän tarvittaessa lisätietoa"
-#: serviceform/templates/serviceform/participation/participation_form/snippets/responsible_contact_person.html:11
-#: serviceform/templates/serviceform/participation/participation_form/snippets/responsible_tooltip.html:11
+#: templates/serviceform/participation/participation_form/snippets/responsible_contact_person.html:11
+#: templates/serviceform/participation/participation_form/snippets/responsible_tooltip.html:11
msgid "Phone"
msgstr "Puhelin"
-#: serviceform/templates/serviceform/participation/participation_form/snippets/responsible_tooltip.html:7
-#: serviceform/templates/serviceform/participation/preview_view.html:8
-#: serviceform/templates/serviceform/reports/view_participant.html:6
-#: serviceform/urls.py:72
+#: templates/serviceform/participation/participation_form/snippets/responsible_tooltip.html:7
+#: templates/serviceform/participation/preview_view.html:8
+#: templates/serviceform/reports/view_participation.html:6 urls.py:73
msgid "Contact details"
msgstr "Yhteystiedot"
-#: serviceform/templates/serviceform/participation/participation_view.html:19
+#: templates/serviceform/participation/participation_view.html:19
msgid ""
"Select all activities that you would like to participate by ticking boxes "
"below."
@@ -1376,7 +1371,7 @@ msgstr ""
"Valitse kaikki tehtävät joihin haluaisit osallistua ruksaamalla "
"valintaruutuja"
-#: serviceform/templates/serviceform/participation/participation_view.html:22
+#: templates/serviceform/participation/participation_view.html:22
msgid ""
"You can get more information about activity by pressing button."
@@ -1384,7 +1379,7 @@ msgstr ""
"Saat lisää tietoa tehtävistä painamalla -"
"nappia."
-#: serviceform/templates/serviceform/participation/participation_view.html:25
+#: templates/serviceform/participation/participation_view.html:25
msgid ""
"\n"
" If you wish to give some extra information about your "
@@ -1400,76 +1395,76 @@ msgstr ""
"kuinka usein haluaisit osallistua), voit antaa lisätietoja tekstiruutuun "
"joka tulee näkyviin kun painat -nappia."
-#: serviceform/templates/serviceform/participation/participation_view.html:66
+#: templates/serviceform/participation/participation_view.html:66
msgid "Go to admin"
msgstr "Lomakkeen hallintapaneeliin"
-#: serviceform/templates/serviceform/participation/participation_view.html:68
-#: serviceform/templates/serviceform/participation/question_view.html:11
+#: templates/serviceform/participation/participation_view.html:68
+#: templates/serviceform/participation/question_view.html:11
msgid "Save and continue"
msgstr "Tallenna ja jatka"
-#: serviceform/templates/serviceform/participation/participation_view.html:74
-#: serviceform/templates/serviceform/participation/question_view.html:15
+#: templates/serviceform/participation/participation_view.html:74
+#: templates/serviceform/participation/question_view.html:15
msgid "Are you sure you want to leave form unsaved?"
msgstr "Oletko varma että haluat jättää lomakkeen tallentamatta?"
-#: serviceform/templates/serviceform/participation/preview_view.html:6
+#: templates/serviceform/participation/preview_view.html:6
msgid "Please check that the given data is correct "
msgstr "Tarkista että antamasi tiedot ovat oikein "
-#: serviceform/templates/serviceform/participation/preview_view.html:24
+#: templates/serviceform/participation/preview_view.html:24
msgid "Change contact details"
msgstr "Muuta yhteystietoja"
-#: serviceform/templates/serviceform/participation/preview_view.html:30
-#: serviceform/templates/serviceform/reports/view_participant.html:22
+#: templates/serviceform/participation/preview_view.html:30
+#: templates/serviceform/reports/view_participation.html:22
msgid "I am wishing to participate in the following activities"
msgstr "Haluan osallistua seuraaviin toimintamuotoihin"
-#: serviceform/templates/serviceform/participation/preview_view.html:36
-#: serviceform/templates/serviceform/reports/snippets/_participant_row.html:59
-#: serviceform/templates/serviceform/reports/view_participant.html:28
+#: templates/serviceform/participation/preview_view.html:36
+#: templates/serviceform/reports/snippets/_participation_row.html:59
+#: templates/serviceform/reports/view_participation.html:28
msgid "Additional info"
msgstr "Lisätietoja"
-#: serviceform/templates/serviceform/participation/preview_view.html:63
+#: templates/serviceform/participation/preview_view.html:63
msgid "Adjust participation information"
msgstr "Muuta osallistumistietoja"
-#: serviceform/templates/serviceform/participation/preview_view.html:70
-#: serviceform/templates/serviceform/reports/contents/_all_questions.html:2
-#: serviceform/templates/serviceform/reports/contents/_responsible_contents.html:42
-#: serviceform/templates/serviceform/reports/view_participant.html:57
+#: templates/serviceform/participation/preview_view.html:70
+#: templates/serviceform/reports/contents/_all_questions.html:2
+#: templates/serviceform/reports/contents/_responsible_contents.html:50
+#: templates/serviceform/reports/view_participation.html:57
msgid "Answers to questions"
msgstr "Vastaukset kysymyksiin"
-#: serviceform/templates/serviceform/participation/preview_view.html:76
-#: serviceform/templates/serviceform/reports/view_participant.html:63
+#: templates/serviceform/participation/preview_view.html:76
+#: templates/serviceform/reports/view_participation.html:63
msgid "Answer"
msgstr "Vastaus"
-#: serviceform/templates/serviceform/participation/preview_view.html:100
+#: templates/serviceform/participation/preview_view.html:100
msgid "Modify question answers"
msgstr "Muuta vastauksia"
-#: serviceform/templates/serviceform/participation/preview_view.html:106
+#: templates/serviceform/participation/preview_view.html:106
msgid "Are you ready to send data?"
msgstr "Oletko valmis lähettämään tiedot?"
-#: serviceform/templates/serviceform/participation/preview_view.html:111
+#: templates/serviceform/participation/preview_view.html:111
msgid "Send data!"
msgstr "Lähetä tiedot!"
-#: serviceform/templates/serviceform/participation/question_view.html:6
+#: templates/serviceform/participation/question_view.html:6
msgid "Please answer following questions."
msgstr "Vastaa seuraaviin kysymyksiin."
-#: serviceform/templates/serviceform/participation/submitted_view.html:5
+#: templates/serviceform/participation/submitted_view.html:5
msgid "Thank you!"
msgstr "Kiitos!"
-#: serviceform/templates/serviceform/participation/submitted_view.html:7
+#: templates/serviceform/participation/submitted_view.html:7
msgid ""
"Your data was saved into the system and related responsibles will be "
"informed about your participation."
@@ -1477,7 +1472,7 @@ msgstr ""
"Antamasi tiedot tallennettiin järjestelmään ja tehtävien vastuuhenkilöille "
"välitetään tieto halukkuudestasi osallistua tehtäviin."
-#: serviceform/templates/serviceform/participation/submitted_view.html:8
+#: templates/serviceform/participation/submitted_view.html:8
msgid ""
"You will receive also email that contains a link that you can use to edit "
"your participation."
@@ -1486,84 +1481,84 @@ msgstr ""
"mikäli haluat myöhemmin muuttaa tietojasi tai ilmoittaa halukkuudestasi "
"osallistua uusiin tehtäviin."
-#: serviceform/templates/serviceform/participation/submitted_view.html:11
+#: templates/serviceform/participation/submitted_view.html:11
msgid "Fill a new form"
msgstr "Täytä uusi lomake"
-#: serviceform/templates/serviceform/reports/_report_navbar.html:7
+#: templates/serviceform/reports/_report_navbar.html:7
msgid "Actions"
msgstr "Toiminnot"
-#: serviceform/templates/serviceform/reports/base/anonymous_report_base.html:4
-#: serviceform/templates/serviceform/reports/base/report_base.html:4
+#: templates/serviceform/reports/base/anonymous_report_base.html:4
+#: templates/serviceform/reports/base/report_base.html:4
msgid "Report for"
msgstr "Raportit lomakkeelle"
-#: serviceform/templates/serviceform/reports/contents/_all_activities.html:4
+#: templates/serviceform/reports/contents/_all_activities.html:4
msgid "All activities"
msgstr "Kaikki tehtävät"
-#: serviceform/templates/serviceform/reports/contents/_all_participants.html:2
-msgid "All participants"
+#: templates/serviceform/reports/contents/_all_participations.html:2
+msgid "All participations"
msgstr "Kaikki osallistujat"
-#: serviceform/templates/serviceform/reports/contents/_all_responsibles.html:2
+#: templates/serviceform/reports/contents/_all_responsibles.html:2
msgid "Responsible contact persons"
msgstr "Vastuussa olevat yhteyshenkilöt"
-#: serviceform/templates/serviceform/reports/contents/_responsible_contents.html:2
+#: templates/serviceform/reports/contents/_responsible_contents.html:2
msgid "Participation to activities"
msgstr "Tehtävien osallistumistiedot"
-#: serviceform/templates/serviceform/reports/edit_responsible.html:4
+#: templates/serviceform/reports/edit_responsible.html:4
msgid "Edit details"
msgstr "Muokkaa tietoja"
-#: serviceform/templates/serviceform/reports/invite.html:4
+#: templates/serviceform/reports/invite.html:4
msgid "Send invitations"
msgstr "Lähetä kutsuja"
-#: serviceform/templates/serviceform/reports/responsible.html:13
-#: serviceform/templates/serviceform/reports/responsible_anonymous.html:12
+#: templates/serviceform/reports/responsible.html:13
+#: templates/serviceform/reports/responsible_anonymous.html:12
msgid "Responsible person contact details"
msgstr "Vastuussa olevan yhteyshenkilön yhteystiedot"
-#: serviceform/templates/serviceform/reports/responsible.html:16
-#: serviceform/templates/serviceform/reports/responsible_anonymous.html:15
+#: templates/serviceform/reports/responsible.html:16
+#: templates/serviceform/reports/responsible_anonymous.html:15
msgid "Can see full report"
msgstr "Voi nähdä koko raportin"
-#: serviceform/templates/serviceform/reports/responsible.html:20
-#: serviceform/templates/serviceform/reports/responsible_anonymous.html:20
+#: templates/serviceform/reports/responsible.html:20
+#: templates/serviceform/reports/responsible_anonymous.html:20
msgid "Configuration"
msgstr "Valinnat"
-#: serviceform/templates/serviceform/reports/responsible.html:24
-#: serviceform/templates/serviceform/reports/responsible_anonymous.html:28
+#: templates/serviceform/reports/responsible.html:24
+#: templates/serviceform/reports/responsible_anonymous.html:28
msgid "Expand all info"
msgstr "Näytä kaikkien lisätiedot"
-#: serviceform/templates/serviceform/reports/responsible_anonymous.html:24
+#: templates/serviceform/reports/responsible_anonymous.html:24
msgid "Show participations from earlier revisions"
msgstr "Näytä osallistujatiedot aiemmista versioista"
-#: serviceform/templates/serviceform/reports/settings.html:5
+#: templates/serviceform/reports/settings.html:5
msgid "Settings"
msgstr "Asetukset"
-#: serviceform/templates/serviceform/reports/snippets/_help.html:3
-msgid "Each participant rows contains the following data"
+#: templates/serviceform/reports/snippets/_help.html:3
+msgid "Each participation rows contains the following data"
msgstr "Kunkin osallistujan rivi sisältää seuraavat tiedot"
-#: serviceform/templates/serviceform/reports/snippets/_help.html:7
+#: templates/serviceform/reports/snippets/_help.html:7
msgid "Email address"
msgstr "Sähköpostiosoite"
-#: serviceform/templates/serviceform/reports/snippets/_help.html:8
+#: templates/serviceform/reports/snippets/_help.html:8
msgid "Age"
msgstr "Ikä"
-#: serviceform/templates/serviceform/reports/snippets/_help.html:9
+#: templates/serviceform/reports/snippets/_help.html:9
msgid ""
"Number of activities; colors from green (1 activity) to red (10 or more "
"activities)."
@@ -1571,133 +1566,140 @@ msgstr ""
"Tehtävien lukumäärä; väri vihreästä (1 tehtävä) punaiseen (10 tehtävää tai "
"enemmän)"
-#: serviceform/templates/serviceform/reports/snippets/_help.html:10
+#: templates/serviceform/reports/snippets/_help.html:10
msgid "Information buttons; pushing button will give more information"
msgstr "Lisätietopainikkeet; saat lisätietoa painamalla painiketta"
-#: serviceform/templates/serviceform/reports/snippets/_help.html:12
+#: templates/serviceform/reports/snippets/_help.html:12
msgid "More information is given"
msgstr "Osallistuja on jättänyt lisätietoja osallistumisesta"
-#: serviceform/templates/serviceform/reports/snippets/_help.html:14
+#: templates/serviceform/reports/snippets/_help.html:14
msgid "Notes have been entered"
msgstr "Osallistujasta on jätetty muistiinpanoja"
-#: serviceform/templates/serviceform/reports/snippets/_participant_row.html:32
+#: templates/serviceform/reports/snippets/_participation_row.html:32
msgid "Number of activities"
msgstr "Tehtävien lukumäärä"
-#: serviceform/templates/serviceform/reports/snippets/_participant_row.html:64
-#: serviceform/templates/serviceform/reports/view_participant.html:104
+#: templates/serviceform/reports/snippets/_participation_row.html:64
+#: templates/serviceform/reports/view_participation.html:104
msgid "Log"
msgstr "Muistiinpanot"
-#: serviceform/templates/serviceform/reports/view_participant.html:88
+#: templates/serviceform/reports/view_participation.html:88
msgid "Other information"
msgstr "Muita tietoja"
-#: serviceform/templates/serviceform/reports/view_participant.html:113
+#: templates/serviceform/reports/view_participation.html:113
msgid "Add entry"
msgstr "Lisää muistiinpano"
-#: serviceform/templatetags/serviceform_tags.py:234
+#: templatetags/serviceform_tags.py:211
msgid "True"
msgstr "Kyllä"
-#: serviceform/templatetags/serviceform_tags.py:235
+#: templatetags/serviceform_tags.py:212
msgid "False"
msgstr "Ei"
-#: serviceform/urls.py:74
+#: urls.py:75
msgid "Email verification"
msgstr "Sähköpostiosoitteen varmennus"
-#: serviceform/urls.py:76
+#: urls.py:77
msgid "Participation details"
msgstr "Osallistumistiedot"
-#: serviceform/urls.py:82
+#: urls.py:83
msgid "Ready!"
msgstr "Valmis!"
-#: serviceform/urls.py:87 serviceform/urls.py:132
+#: urls.py:88 urls.py:133
msgid "Responsibles"
msgstr "Vastuuhenkilöt"
-#: serviceform/urls.py:91
+#: urls.py:92
msgid "Participations"
msgstr "Osallistumistiedot"
-#: serviceform/urls.py:93
+#: urls.py:94
msgid "Answers"
msgstr "Vastaukset"
-#: serviceform/urls.py:95
+#: urls.py:96
msgid "My report"
msgstr "Oma raportti"
-#: serviceform/urls.py:100
+#: urls.py:101
msgid "Report settings"
msgstr "Raportin asetukset"
-#: serviceform/urls.py:104
+#: urls.py:105
msgid "Edit form"
msgstr "Muokkaa lomaketta"
-#: serviceform/urls.py:109 serviceform/urls.py:124
+#: urls.py:110 urls.py:125
msgid "Log out"
msgstr "Kirjaudu ulos"
-#: serviceform/urls.py:114
+#: urls.py:115
msgid "Your report"
msgstr "Raporttisi"
-#: serviceform/urls.py:117
+#: urls.py:118
msgid "Edit your contact details"
msgstr "Muokkaa yhteystietojasi"
-#: serviceform/urls.py:120
+#: urls.py:121
msgid "To full report"
msgstr "Koko raportti"
-#: serviceform/urls.py:128
+#: urls.py:129
msgid "Password login"
msgstr "Kirjautuminen salasanalla"
-#: serviceform/urls.py:130
+#: urls.py:131
msgid "Former users"
msgstr "Aikaisemmat käyttäjät"
-#: serviceform/urls.py:134
+#: urls.py:135
msgid "Admin login"
msgstr "Ylläpidon kirjautuminen"
-#: serviceform/utils.py:111
+#: utils.py:112
msgid "User is not allowed to access document"
msgstr "Käyttäjällä ei ole pääsyä dokumenttiin"
-#: serviceform/utils.py:375
+#: utils.py:168
+msgid "Copy of {}"
+msgstr "Lomakkeen {} kopio"
+
+#: utils.py:172
+msgid "new-revision"
+msgstr "uusi-versio"
+
+#: utils.py:429
msgid "Your authentication URL was expired. New link has been sent to {}"
msgstr "Avainlinkkisi on vanhentunut. Uusi linkki on lähetetty osoitteeseen {}"
-#: serviceform/views/participation_views.py:70
+#: views/participation_views.py:73
msgid "Verification email already sent to {}, not sending again."
msgstr ""
"Sähköpostin varmennusviesti on jo lähetetty osoitteeseen {}. Viestiä ei "
"lähetetä uudelleen."
-#: serviceform/views/participation_views.py:161
+#: views/participation_views.py:162
msgid "Authentication link was sent to email address {}."
msgstr ""
"Lomakkeen tietojen muokkaamisen mahdollistava linkki lähetettiin "
"sähköpostiosoitteeseen {}."
-#: serviceform/views/participation_views.py:170
+#: views/participation_views.py:170
msgid "Your email {} is now verified successfully!"
msgstr "Sähköpostiosoitteesi {} on nyt varmennettu!"
-#: serviceform/views/participation_views.py:204
-#: serviceform/views/reports_views.py:51
+#: views/participation_views.py:202 views/reports_views.py:46
msgid ""
"Given URL might be expired. Please give your email address and we'll send "
"you a new link"
@@ -1705,38 +1707,26 @@ msgstr ""
"Antamasi linkki voi olla vanhentunut tai muuten virheellinen. Anna "
"sähköpostiosoitteesi niin lähetämme sinulle uuden linkin."
-#: serviceform/views/participation_views.py:233
+#: views/participation_views.py:230
msgid "Your participation was deleted"
msgstr "Osallistumistietosi poistettiin"
-#: serviceform/views/reports_views.py:80
+#: views/reports_views.py:71
msgid "Settings saved"
msgstr "Asetukset tallennettu"
-#: serviceform/views/reports_views.py:82
+#: views/reports_views.py:73
msgid "Settings could not be saved"
msgstr "Asetuksia ei voitu tallentaa"
-#: serviceform/views/reports_views.py:190
+#: views/reports_views.py:168
msgid "Saved contact details"
msgstr "Yhteystiedot tallennettu"
-#: serviceform/views/reports_views.py:210
+#: views/reports_views.py:186
msgid "You have been logged out"
msgstr "Olet kirjautunut ulos"
-#~ msgid "Copy form as a new"
-#~ msgstr "Kopioi lomake uudeksi"
-
-#~ msgid "Send mail"
-#~ msgstr "Lähetä sähköpostia"
-
-#~ msgid "Copy of {}"
-#~ msgstr "Lomakkeen {} kopio"
-
-#~ msgid "new-revision"
-#~ msgstr "uusi-versio"
-
#~ msgid "Allowed users"
#~ msgstr "Sallitut käyttäjät"
@@ -1842,7 +1832,7 @@ msgstr "Olet kirjautunut ulos"
#~ msgstr "Yhteyshenkilö"
#~ msgid ""
-#~ "Send email to responsible person whenever a participant fills and sends a "
+#~ "Send email to responsible person whenever a participation fills and sends a "
#~ "form"
#~ msgstr ""
#~ "Lähetä sähköpostia vastuuhenkilölle aina kun uusi lomake on täytetty"
diff --git a/serviceform/serviceform/management/commands/create_default_email_templates.py b/serviceform/serviceform/management/commands/create_default_email_templates.py
index a3d98e6..034ad73 100644
--- a/serviceform/serviceform/management/commands/create_default_email_templates.py
+++ b/serviceform/serviceform/management/commands/create_default_email_templates.py
@@ -19,7 +19,7 @@
from django.core.management import BaseCommand
from django.utils.translation import activate
from django.conf import settings
-from serviceform.serviceform.models import ServiceForm
+from serviceform.serviceform.models import ServiceForm, Organization
class Command(BaseCommand):
@@ -30,3 +30,5 @@ def handle(self, *args, **kwargs):
activate(settings.LANGUAGE_CODE)
for s in ServiceForm.objects.all():
s.create_email_templates()
+ for o in Organization.objects.all():
+ o.create_email_templates()
diff --git a/serviceform/serviceform/migrations/0001_initial.py b/serviceform/serviceform/migrations/0001_initial.py
index 379b0fb..d9b06ce 100644
--- a/serviceform/serviceform/migrations/0001_initial.py
+++ b/serviceform/serviceform/migrations/0001_initial.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# Generated by Django 1.11.1 on 2017-05-22 10:48
+# Generated by Django 1.11.3 on 2017-07-07 06:56
from __future__ import unicode_literals
import datetime
@@ -10,71 +10,17 @@
import django.db.models.deletion
from django.utils.timezone import utc
import select2.fields
-import serviceform.serviceform.models
+import serviceform.serviceform.fields
import serviceform.serviceform.utils
-
-def add_basic_rights_groups_and_permissions(apps, schema_editor):
- full = ['activity',
- 'activitychoice',
- 'formrevision',
- 'level1category',
- 'level2category',
- 'question',
- 'responsibilityperson',
- 'serviceform',
- 'emailtemplate',
- ]
- delete = [
- 'participant',
- 'participationactivity',
- 'participationactivitychoice',
- 'questionanswer',
- ]
-
- custom_permissions = []
-
- Permission = apps.get_model('auth', 'Permission')
- Group = apps.get_model('auth', 'Group')
- ContentType = apps.get_model('contenttypes', 'ContentType')
- grp, created = Group.objects.get_or_create(name='Serviceform basic rights')
-
- def add_perm(_model, _action):
- print('Trying to add permission %s to %s' %(_action, _model))
- ct, created = ContentType.objects.get_or_create(app_label='serviceform', model=_model)
- codename = '%s_%s' % (_action, _model)
- try:
- p = Permission.objects.get(codename=codename, content_type=ct)
- except Permission.DoesNotExist:
- p = Permission.objects.create(codename=codename, name=codename.capitalize().replace('_', ' '), content_type=ct)
-
- grp.permissions.add(p)
-
- for model in full:
- for action in ['add', 'change', 'delete']:
- add_perm(model, action)
-
- for model in delete:
- add_perm(model, 'delete')
-
- ct = ContentType.objects.get(app_label='serviceform', model='serviceform')
- p = Permission.objects.create(codename='can_access_serviceform',
- content_type=ct, name='Can access Service Form')
-
-
-def null(apps_schema_editor):
- pass
-
-
-
class Migration(migrations.Migration):
initial = True
dependencies = [
- ('contenttypes', '0002_remove_content_type_name'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('contenttypes', '0002_remove_content_type_name'),
]
operations = [
@@ -85,9 +31,9 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=256, verbose_name='Name')),
('description', models.TextField(blank=True, verbose_name='Description')),
('order', models.PositiveIntegerField(db_index=True, default=0, verbose_name='Order')),
+ ('skip_numbering', models.BooleanField(default=False, verbose_name='Skip')),
('multiple_choices_allowed', models.BooleanField(default=True, verbose_name='Multichoice')),
('people_needed', models.PositiveIntegerField(default=0, verbose_name='Needed')),
- ('skip_numbering', models.BooleanField(default=False, verbose_name='Skip')),
],
options={
'verbose_name': 'Activity',
@@ -95,7 +41,6 @@ class Migration(migrations.Migration):
'ordering': ('order',),
'abstract': False,
},
- bases=(serviceform.serviceform.models.SubitemMixin, models.Model),
),
migrations.CreateModel(
name='ActivityChoice',
@@ -104,8 +49,8 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=256, verbose_name='Name')),
('description', models.TextField(blank=True, verbose_name='Description')),
('order', models.PositiveIntegerField(db_index=True, default=0, verbose_name='Order')),
- ('people_needed', models.PositiveIntegerField(default=0, verbose_name='Needed')),
('skip_numbering', models.BooleanField(default=False, verbose_name='Skip')),
+ ('people_needed', models.PositiveIntegerField(default=0, verbose_name='Needed')),
('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.Activity')),
],
options={
@@ -114,7 +59,6 @@ class Migration(migrations.Migration):
'ordering': ('order',),
'abstract': False,
},
- bases=(serviceform.serviceform.models.SubitemMixin, models.Model),
),
migrations.CreateModel(
name='EmailMessage',
@@ -136,13 +80,12 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=256, verbose_name='Template name')),
('subject', models.CharField(max_length=256, verbose_name='Subject')),
- ('content', models.TextField(help_text='Following context may (depending on topic) be available for both subject and content: {{responsible}}, {{participant}}, {{last_modified}}, {{form}}, {{url}}, {{contact}}', verbose_name='Content')),
+ ('content', models.TextField(help_text='Following context may (depending on topic) be available for both subject and content: {{responsible}}, {{participation}}, {{last_modified}}, {{form}}, {{url}}, {{contact}}', verbose_name='Content')),
],
options={
'verbose_name': 'Email template',
'verbose_name_plural': 'Email templates',
},
- bases=(serviceform.serviceform.models.CopyMixin, models.Model),
),
migrations.CreateModel(
name='FormRevision',
@@ -151,7 +94,7 @@ class Migration(migrations.Migration):
('name', models.SlugField(max_length=32, verbose_name='Revision name')),
('valid_from', models.DateTimeField(default=datetime.datetime(2999, 12, 31, 22, 20, tzinfo=utc), verbose_name='Valid from')),
('valid_to', models.DateTimeField(default=datetime.datetime(2999, 12, 31, 22, 20, tzinfo=utc), verbose_name='Valid to')),
- ('send_bulk_email_to_participants', models.BooleanField(default=True, help_text='Send email to participants that filled the form when this revision was active. Email is sent when new current revision is published.', verbose_name='Send bulk email to participants')),
+ ('send_bulk_email_to_participations', models.BooleanField(default=True, help_text='Send email to participations that filled the form when this revision was active. Email is sent when new current revision is published.', verbose_name='Send bulk email to participations')),
('send_emails_after', models.DateTimeField(default=datetime.datetime(2999, 12, 31, 22, 20, tzinfo=utc), help_text='Sends bulk email to responsibility persons at specified time, after which it will send email for each new participation', verbose_name='Email sending starts')),
],
options={
@@ -167,7 +110,8 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=256, verbose_name='Name')),
('description', models.TextField(blank=True, verbose_name='Description')),
('order', models.PositiveIntegerField(db_index=True, default=0, verbose_name='Order')),
- ('background_color', serviceform.serviceform.models.ColorField(blank=True, null=True, verbose_name='Background color')),
+ ('skip_numbering', models.BooleanField(default=False, verbose_name='Skip')),
+ ('background_color', serviceform.serviceform.fields.ColorField(blank=True, null=True, verbose_name='Background color')),
],
options={
'verbose_name': 'Level 1 category',
@@ -175,7 +119,6 @@ class Migration(migrations.Migration):
'ordering': ('order',),
'abstract': False,
},
- bases=(serviceform.serviceform.models.SubitemMixin, models.Model),
),
migrations.CreateModel(
name='Level2Category',
@@ -184,7 +127,8 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=256, verbose_name='Name')),
('description', models.TextField(blank=True, verbose_name='Description')),
('order', models.PositiveIntegerField(db_index=True, default=0, verbose_name='Order')),
- ('background_color', serviceform.serviceform.models.ColorField(blank=True, null=True, verbose_name='Background color')),
+ ('skip_numbering', models.BooleanField(default=False, verbose_name='Skip')),
+ ('background_color', serviceform.serviceform.fields.ColorField(blank=True, null=True, verbose_name='Background color')),
('category', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='serviceform.Level1Category', verbose_name='Level 1 category')),
],
options={
@@ -193,47 +137,54 @@ class Migration(migrations.Migration):
'ordering': ('order',),
'abstract': False,
},
- bases=(serviceform.serviceform.models.SubitemMixin, models.Model),
),
migrations.CreateModel(
- name='Participant',
+ name='Member',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('forenames', models.CharField(max_length=64, verbose_name='Forename(s)')),
('surname', models.CharField(max_length=64, verbose_name='Surname')),
- ('street_address', models.CharField(max_length=128, verbose_name='Street address')),
- ('postal_code', models.CharField(max_length=32, validators=[django.core.validators.RegexValidator(code='invalid', message='Enter a valid postal code.', regex='^\\d{5}$')], verbose_name='Zip/Postal code')),
- ('city', models.CharField(max_length=32, verbose_name='City')),
- ('email', models.EmailField(db_index=True, max_length=254, verbose_name='Email')),
- ('phone_number', models.CharField(max_length=32, validators=[django.core.validators.RegexValidator(message="Phone number must be entered in the format: '050123123' or '+35850123123'. Up to 15 digits allowed.", regex='^\\+?1?\\d{9,15}$')], verbose_name='Phone number')),
+ ('street_address', models.CharField(blank=True, max_length=128, verbose_name='Street address')),
+ ('postal_code', models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator(code='invalid', message='Enter a valid postal code.', regex='^\\d{5}$')], verbose_name='Zip/Postal code')),
+ ('city', models.CharField(blank=True, max_length=32, verbose_name='City')),
+ ('year_of_birth', models.SmallIntegerField(blank=True, null=True, verbose_name='Year of birth')),
+ ('email', models.EmailField(blank=True, db_index=True, max_length=254, verbose_name='Email')),
+ ('email_verified', models.BooleanField(default=False, verbose_name='Email verified')),
+ ('phone_number', models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator(message="Phone number must be entered in the format: '050123123' or '+35850123123'. Up to 15 digits allowed.", regex='^\\+?1?\\d{9,15}$')], verbose_name='Phone number')),
+ ('membership_type', models.CharField(choices=[('external', 'external'), ('normal', 'normal'), ('staff', 'staff')], default='external', max_length=8, verbose_name='Is this person a member of this organization?')),
('auth_keys_hash_storage', django.contrib.postgres.fields.jsonb.JSONField(default=[])),
('secret_key', models.CharField(db_index=True, default=serviceform.serviceform.utils.generate_uuid, max_length=36, unique=True, verbose_name='Secret key')),
- ('year_of_birth', models.SmallIntegerField(blank=True, null=True, verbose_name='Year of birth')),
+ ('allow_responsible_email', models.BooleanField(default=True, help_text='Send email notifications whenever new participation to administered activities is registered. Email contains also has a link that allows accessing raport of administered activities.', verbose_name='Send email notifications')),
+ ('allow_participation_email', models.BooleanField(default=True, help_text='You will receive email that contains a link that allows later modification of the form. Also when new version of form is published, you will be notified. It is highly recommended that you keep this enabled unless you move away and do not want to participate at all any more. You can also change this setting later if you wish.', verbose_name='Send email notifications')),
+ ('hide_contact_details', models.BooleanField(default=False, verbose_name='Hide contact details in form')),
+ ('show_full_report', models.BooleanField(default=False, verbose_name='Grant access to full reports')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Organization',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=64, verbose_name='Organization name')),
+ ('email_to_member_auth_link', models.ForeignKey(blank=True, help_text='Email that is sent to member when auth link is requested', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Auth link email to member')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Participation',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('status', models.CharField(choices=[('invited', 'invited'), ('ongoing', 'ongoing'), ('updating', 'updating'), ('finished', 'finished')], default='ongoing', max_length=16)),
('last_finished_view', models.CharField(default='', max_length=32)),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Last modified')),
('last_finished', models.DateTimeField(null=True, verbose_name='Last finished')),
- ('email_verified', models.BooleanField(default=False, verbose_name='Email verified')),
- ('send_email_allowed', models.BooleanField(default=True, help_text='You will receive email that contains a link that allows later modification of the form. Also when new version of form is published, you will be notified. It is highly recommended that you keep this enabled unless you move away and do not want to participate at all any more. You can also change this setting later if you wish.', verbose_name='Sending email allowed')),
('form_revision', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='serviceform.FormRevision')),
+ ('member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.Member')),
],
options={
- 'verbose_name': 'Participant',
- 'verbose_name_plural': 'Participants',
+ 'verbose_name': 'Participation',
+ 'verbose_name_plural': 'Participations',
},
),
- migrations.CreateModel(
- name='ParticipantLog',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('created_at', models.DateTimeField(auto_now_add=True)),
- ('writer_id', models.PositiveIntegerField()),
- ('message', models.TextField()),
- ('participant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.Participant')),
- ('writer_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
- ],
- ),
migrations.CreateModel(
name='ParticipationActivity',
fields=[
@@ -241,7 +192,7 @@ class Migration(migrations.Migration):
('additional_info', models.CharField(blank=True, max_length=1024, null=True)),
('created_at', models.DateTimeField(auto_now_add=True, null=True)),
('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.Activity')),
- ('participant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.Participant')),
+ ('participation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.Participation')),
],
options={
'ordering': ('activity__category__category__order', 'activity__category__order', 'activity__order'),
@@ -260,11 +211,25 @@ class Migration(migrations.Migration):
'ordering': ('activity_choice__order',),
},
),
+ migrations.CreateModel(
+ name='ParticipationLog',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('writer_id', models.PositiveIntegerField()),
+ ('message', models.TextField()),
+ ('participation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.Participation')),
+ ('writer_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
+ ],
+ ),
migrations.CreateModel(
name='Question',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=256, verbose_name='Name')),
+ ('description', models.TextField(blank=True, verbose_name='Description')),
('order', models.PositiveIntegerField(db_index=True, default=0, verbose_name='Order')),
+ ('skip_numbering', models.BooleanField(default=False, verbose_name='Skip')),
('question', models.CharField(max_length=1024, verbose_name='Question')),
('answer_type', models.CharField(choices=[('integer', 'Integer'), ('short_text', 'Short text'), ('long_text', 'Long text'), ('boolean', 'Boolean'), ('date', 'Date')], default='short_text', max_length=16, verbose_name='Answer type')),
('required', models.BooleanField(default=False, verbose_name='Answer required?')),
@@ -275,7 +240,6 @@ class Migration(migrations.Migration):
'ordering': ('order',),
'abstract': False,
},
- bases=(serviceform.serviceform.models.CopyMixin, models.Model),
),
migrations.CreateModel(
name='QuestionAnswer',
@@ -283,53 +247,31 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('answer', models.TextField()),
('created_at', models.DateTimeField(auto_now_add=True, null=True)),
- ('participant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.Participant')),
+ ('participation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.Participation')),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.Question')),
],
options={
'ordering': ('question__order',),
},
),
- migrations.CreateModel(
- name='ResponsibilityPerson',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('forenames', models.CharField(max_length=64, verbose_name='Forename(s)')),
- ('surname', models.CharField(max_length=64, verbose_name='Surname')),
- ('street_address', models.CharField(blank=True, max_length=128, verbose_name='Street address')),
- ('postal_code', models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator(code='invalid', message='Enter a valid postal code.', regex='^\\d{5}$')], verbose_name='Zip/Postal code')),
- ('city', models.CharField(blank=True, max_length=32, verbose_name='City')),
- ('email', models.EmailField(db_index=True, max_length=254, verbose_name='Email')),
- ('phone_number', models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator(message="Phone number must be entered in the format: '050123123' or '+35850123123'. Up to 15 digits allowed.", regex='^\\+?1?\\d{9,15}$')], verbose_name='Phone number')),
- ('auth_keys_hash_storage', django.contrib.postgres.fields.jsonb.JSONField(default=[])),
- ('secret_key', models.CharField(db_index=True, default=serviceform.serviceform.utils.generate_uuid, max_length=36, unique=True, verbose_name='Secret key')),
- ('send_email_notifications', models.BooleanField(default=True, help_text='Send email notifications whenever new participation to administered activities is registered. Email contains also has a link that allows accessing raport of administered activities.', verbose_name='Send email notifications')),
- ('hide_contact_details', models.BooleanField(default=False, verbose_name='Hide contact details in form')),
- ('show_full_report', models.BooleanField(default=False, verbose_name='Grant access to full reports')),
- ],
- options={
- 'verbose_name': 'Responsibility person',
- 'verbose_name_plural': 'Responsibility persons',
- 'ordering': ('surname',),
- },
- bases=(serviceform.serviceform.models.CopyMixin, models.Model),
- ),
migrations.CreateModel(
name='ServiceForm',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('order', models.PositiveIntegerField(db_index=True, default=0, verbose_name='Order')),
+ ('skip_numbering', models.BooleanField(default=False, verbose_name='Skip')),
('name', models.CharField(max_length=256, verbose_name='Name of the serviceform')),
('slug', models.SlugField(help_text='Tämä on osa lomakkeen osoitetta, ts. lomakkeen osoitteeksi tulee http://localhost:8000/valitsemasi-nimi-urlissa', unique=True, verbose_name='Slug')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')),
('last_updated', models.DateTimeField(auto_now=True, null=True, verbose_name='Last updated')),
('require_email_verification', models.BooleanField(default=True, verbose_name='Require email verification')),
- ('password', models.CharField(blank=True, default='', help_text='Password that is asked from participants', max_length=32, verbose_name='Password')),
+ ('password', models.CharField(blank=True, default='', help_text='Password that is asked from participations', max_length=32, verbose_name='Password')),
('hide_contact_details', models.BooleanField(default=False, verbose_name='Hide contact details (other than email) in form')),
('flow_by_categories', models.BooleanField(default=False, help_text='Please note that preview shows full form despite this option', verbose_name='Split participation form to level 1 categories')),
('allow_skipping_categories', models.BooleanField(default=False, help_text='In effect only if flow by categories option is enabled. If this option is enabled, user can jump between categories. If disabled, he must proceed them one by one.', verbose_name='Allow jumping between categories')),
- ('level1_color', serviceform.serviceform.models.ColorField(blank=True, help_text='If left blank (black), default coloring will be used', null=True, verbose_name='Level 1 category default background color')),
- ('level2_color', serviceform.serviceform.models.ColorField(blank=True, help_text='If left blank (black), it will be derived from level 1 background color', null=True, verbose_name='Level 2 category default background color')),
- ('activity_color', serviceform.serviceform.models.ColorField(blank=True, help_text='If left blank (black), it will be derived from level 2 background color', null=True, verbose_name='Activity default background color')),
+ ('level1_color', serviceform.serviceform.fields.ColorField(blank=True, help_text='If left blank (black), default coloring will be used', null=True, verbose_name='Level 1 category default background color')),
+ ('level2_color', serviceform.serviceform.fields.ColorField(blank=True, help_text='If left blank (black), it will be derived from level 1 background color', null=True, verbose_name='Level 2 category default background color')),
+ ('activity_color', serviceform.serviceform.fields.ColorField(blank=True, help_text='If left blank (black), it will be derived from level 2 background color', null=True, verbose_name='Activity default background color')),
('description', models.TextField(blank=True, help_text='Description box will be shown before instruction box in participation view.', verbose_name='Description')),
('instructions', models.TextField(blank=True, help_text='Use HTML formatting. Leave this empty to use default. This is shown in participation view.', null=True, verbose_name='Instructions')),
('login_text', models.TextField(blank=True, help_text='This will be shown in the login screen', null=True, verbose_name='Login text')),
@@ -341,27 +283,23 @@ class Migration(migrations.Migration):
('visible_phone_number', models.BooleanField(default=True, verbose_name='Phone number')),
('bulk_email_to_responsibles', models.ForeignKey(blank=True, help_text='Email that is sent to responsibles when emailing starts', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Bulk email to responsibles')),
('current_revision', models.ForeignKey(blank=True, help_text='You need to first add a revision to form (see below) and save. Then newly created revision will appear in the list.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='serviceform.FormRevision', verbose_name='Current revision')),
- ('email_to_former_participants', models.ForeignKey(blank=True, help_text='Email that is sent to former participants when form is published', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Bulk email to former participants')),
+ ('email_to_former_participations', models.ForeignKey(blank=True, help_text='Email that is sent to former participations when form is published', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Bulk email to former participations')),
('email_to_invited_users', models.ForeignKey(blank=True, help_text='Email that is sent when user is invited to the form manually via invite form', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Invite email')),
- ('email_to_participant', models.ForeignKey(blank=True, help_text='Email that is sent to participant after he has fulfilled his participation', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Email to participant, on finish')),
- ('email_to_participant_on_update', models.ForeignKey(blank=True, help_text='Email that is sent to participant after he has updated his participation', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Email to participant, on update')),
+ ('email_to_participation', models.ForeignKey(blank=True, help_text='Email that is sent to participation after he has fulfilled his participation', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Email to participation, on finish')),
+ ('email_to_participation_on_update', models.ForeignKey(blank=True, help_text='Email that is sent to participation after he has updated his participation', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Email to participation, on update')),
('email_to_responsible_auth_link', models.ForeignKey(blank=True, help_text='Email that is sent to responsible when he requests auth link', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Responsible requests auth link')),
('email_to_responsibles', models.ForeignKey(blank=True, help_text='Email that is sent to responsibles when new participation is registered', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Email to responsibles')),
('last_editor', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='last_edited_serviceform', to=settings.AUTH_USER_MODEL, verbose_name='Last edited by')),
- ('resend_email_to_participant', models.ForeignKey(blank=True, help_text='Email that is sent to participant if he requests resending email', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Resend email to participant')),
- ('responsible', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='serviceform.ResponsibilityPerson', verbose_name='Responsible')),
- ('verification_email_to_participant', models.ForeignKey(blank=True, help_text='Email verification message that is sent to participant when filling form, if email verification is enabled', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Verification email to participant')),
+ ('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.Organization')),
+ ('resend_email_to_participation', models.ForeignKey(blank=True, help_text='Email that is sent to participation if he requests resending email', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Resend email to participation')),
+ ('responsible', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='serviceform.Member', verbose_name='Responsible')),
+ ('responsibles', select2.fields.ManyToManyField(blank=True, related_name='serviceform_responsibles', sorted=False, to='serviceform.Member', verbose_name='Responsible persons')),
+ ('verification_email_to_participation', models.ForeignKey(blank=True, help_text='Email verification message that is sent to participation when filling form, if email verification is enabled', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Verification email to participation')),
],
options={
'verbose_name': 'Service form',
'verbose_name_plural': 'Service forms',
},
- bases=(serviceform.serviceform.models.SubitemMixin, models.Model),
- ),
- migrations.AddField(
- model_name='responsibilityperson',
- name='form',
- field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='serviceform.ServiceForm'),
),
migrations.AddField(
model_name='question',
@@ -371,12 +309,17 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='question',
name='responsibles',
- field=select2.fields.ManyToManyField(blank=True, related_name='question_related', sorted=False, to='serviceform.ResponsibilityPerson', verbose_name='Responsible persons'),
+ field=select2.fields.ManyToManyField(blank=True, related_name='question_responsibles', sorted=False, to='serviceform.Member', verbose_name='Responsible persons'),
+ ),
+ migrations.AddField(
+ model_name='member',
+ name='organization',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.Organization'),
),
migrations.AddField(
model_name='level2category',
name='responsibles',
- field=select2.fields.ManyToManyField(blank=True, related_name='level2category_related', sorted=False, to='serviceform.ResponsibilityPerson', verbose_name='Responsible persons'),
+ field=select2.fields.ManyToManyField(blank=True, related_name='level2category_responsibles', sorted=False, to='serviceform.Member', verbose_name='Responsible persons'),
),
migrations.AddField(
model_name='level1category',
@@ -386,7 +329,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='level1category',
name='responsibles',
- field=select2.fields.ManyToManyField(blank=True, related_name='level1category_related', sorted=False, to='serviceform.ResponsibilityPerson', verbose_name='Responsible persons'),
+ field=select2.fields.ManyToManyField(blank=True, related_name='level1category_responsibles', sorted=False, to='serviceform.Member', verbose_name='Responsible persons'),
),
migrations.AddField(
model_name='formrevision',
@@ -395,8 +338,8 @@ class Migration(migrations.Migration):
),
migrations.AddField(
model_name='emailtemplate',
- name='form',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.ServiceForm'),
+ name='organization',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='serviceform.Organization'),
),
migrations.AddField(
model_name='emailmessage',
@@ -406,7 +349,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='activitychoice',
name='responsibles',
- field=select2.fields.ManyToManyField(blank=True, related_name='activitychoice_related', sorted=False, to='serviceform.ResponsibilityPerson', verbose_name='Responsible persons'),
+ field=select2.fields.ManyToManyField(blank=True, related_name='activitychoice_responsibles', sorted=False, to='serviceform.Member', verbose_name='Responsible persons'),
),
migrations.AddField(
model_name='activity',
@@ -416,7 +359,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='activity',
name='responsibles',
- field=select2.fields.ManyToManyField(blank=True, related_name='activity_related', sorted=False, to='serviceform.ResponsibilityPerson', verbose_name='Responsible persons'),
+ field=select2.fields.ManyToManyField(blank=True, related_name='activity_responsibles', sorted=False, to='serviceform.Member', verbose_name='Responsible persons'),
),
migrations.AlterUniqueTogether(
name='participationactivitychoice',
@@ -424,12 +367,10 @@ class Migration(migrations.Migration):
),
migrations.AlterUniqueTogether(
name='participationactivity',
- unique_together=set([('participant', 'activity')]),
+ unique_together=set([('participation', 'activity')]),
),
migrations.AlterUniqueTogether(
name='formrevision',
unique_together=set([('form', 'name')]),
),
- migrations.RunPython(add_basic_rights_groups_and_permissions, null),
-
]
diff --git a/serviceform/serviceform/migrations_/0001_initial.py b/serviceform/serviceform/migrations_/0001_initial.py
new file mode 100644
index 0000000..b4486d3
--- /dev/null
+++ b/serviceform/serviceform/migrations_/0001_initial.py
@@ -0,0 +1,434 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2017-05-22 10:48
+from __future__ import unicode_literals
+
+import datetime
+from django.conf import settings
+import django.contrib.postgres.fields.jsonb
+import django.core.validators
+from django.db import migrations, models
+import django.db.models.deletion
+from django.utils.timezone import utc
+import select2.fields
+from serviceform.serviceform.fields import ColorField
+from serviceform.serviceform.utils import generate_uuid
+
+
+def add_basic_rights_groups_and_permissions(apps, schema_editor):
+ full = ['activity',
+ 'activitychoice',
+ 'formrevision',
+ 'level1category',
+ 'level2category',
+ 'question',
+ 'responsibilityperson',
+ 'serviceform',
+ 'emailtemplate',
+ ]
+ delete = [
+ 'participant',
+ 'participationactivity',
+ 'participationactivitychoice',
+ 'questionanswer',
+ ]
+
+ custom_permissions = []
+
+ Permission = apps.get_model('auth', 'Permission')
+ Group = apps.get_model('auth', 'Group')
+ ContentType = apps.get_model('contenttypes', 'ContentType')
+ grp, created = Group.objects.get_or_create(name='Serviceform basic rights')
+
+ def add_perm(_model, _action):
+ print('Trying to add permission %s to %s' %(_action, _model))
+ ct, created = ContentType.objects.get_or_create(app_label='serviceform', model=_model)
+ codename = '%s_%s' % (_action, _model)
+ try:
+ p = Permission.objects.get(codename=codename, content_type=ct)
+ except Permission.DoesNotExist:
+ p = Permission.objects.create(codename=codename, name=codename.capitalize().replace('_', ' '), content_type=ct)
+
+ grp.permissions.add(p)
+
+ for model in full:
+ for action in ['add', 'change', 'delete']:
+ add_perm(model, action)
+
+ for model in delete:
+ add_perm(model, 'delete')
+
+ ct = ContentType.objects.get(app_label='serviceform', model='serviceform')
+ p = Permission.objects.create(codename='can_access_serviceform',
+ content_type=ct, name='Can access Service Form')
+
+
+def null(apps_schema_editor):
+ pass
+
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('contenttypes', '0002_remove_content_type_name'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Activity',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=256, verbose_name='Name')),
+ ('description', models.TextField(blank=True, verbose_name='Description')),
+ ('order', models.PositiveIntegerField(db_index=True, default=0, verbose_name='Order')),
+ ('multiple_choices_allowed', models.BooleanField(default=True, verbose_name='Multichoice')),
+ ('people_needed', models.PositiveIntegerField(default=0, verbose_name='Needed')),
+ ('skip_numbering', models.BooleanField(default=False, verbose_name='Skip')),
+ ],
+ options={
+ 'verbose_name': 'Activity',
+ 'verbose_name_plural': 'Activities',
+ 'ordering': ('order',),
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='ActivityChoice',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=256, verbose_name='Name')),
+ ('description', models.TextField(blank=True, verbose_name='Description')),
+ ('order', models.PositiveIntegerField(db_index=True, default=0, verbose_name='Order')),
+ ('people_needed', models.PositiveIntegerField(default=0, verbose_name='Needed')),
+ ('skip_numbering', models.BooleanField(default=False, verbose_name='Skip')),
+ ('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.Activity')),
+ ],
+ options={
+ 'verbose_name': 'Activity choice',
+ 'verbose_name_plural': 'Activity choices',
+ 'ordering': ('order',),
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='EmailMessage',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('last_modified', models.DateTimeField(auto_now=True)),
+ ('from_address', models.CharField(max_length=256)),
+ ('to_address', models.CharField(max_length=256)),
+ ('subject', models.CharField(max_length=256)),
+ ('content', models.TextField()),
+ ('sent_at', models.DateTimeField(null=True)),
+ ('context', models.TextField(default='{}')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='EmailTemplate',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=256, verbose_name='Template name')),
+ ('subject', models.CharField(max_length=256, verbose_name='Subject')),
+ ('content', models.TextField(help_text='Following context may (depending on topic) be available for both subject and content: {{responsible}}, {{participant}}, {{last_modified}}, {{form}}, {{url}}, {{contact}}', verbose_name='Content')),
+ ],
+ options={
+ 'verbose_name': 'Email template',
+ 'verbose_name_plural': 'Email templates',
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='FormRevision',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.SlugField(max_length=32, verbose_name='Revision name')),
+ ('valid_from', models.DateTimeField(default=datetime.datetime(2999, 12, 31, 22, 20, tzinfo=utc), verbose_name='Valid from')),
+ ('valid_to', models.DateTimeField(default=datetime.datetime(2999, 12, 31, 22, 20, tzinfo=utc), verbose_name='Valid to')),
+ ('send_bulk_email_to_participants', models.BooleanField(default=True, help_text='Send email to participants that filled the form when this revision was active. Email is sent when new current revision is published.', verbose_name='Send bulk email to participants')),
+ ('send_emails_after', models.DateTimeField(default=datetime.datetime(2999, 12, 31, 22, 20, tzinfo=utc), help_text='Sends bulk email to responsibility persons at specified time, after which it will send email for each new participation', verbose_name='Email sending starts')),
+ ],
+ options={
+ 'verbose_name': 'Form revision',
+ 'verbose_name_plural': 'Form revisions',
+ 'ordering': ('-valid_from',),
+ },
+ ),
+ migrations.CreateModel(
+ name='Level1Category',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=256, verbose_name='Name')),
+ ('description', models.TextField(blank=True, verbose_name='Description')),
+ ('order', models.PositiveIntegerField(db_index=True, default=0, verbose_name='Order')),
+ ('background_color', ColorField(blank=True, null=True, verbose_name='Background color')),
+ ],
+ options={
+ 'verbose_name': 'Level 1 category',
+ 'verbose_name_plural': 'Level 1 categories',
+ 'ordering': ('order',),
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Level2Category',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=256, verbose_name='Name')),
+ ('description', models.TextField(blank=True, verbose_name='Description')),
+ ('order', models.PositiveIntegerField(db_index=True, default=0, verbose_name='Order')),
+ ('background_color', ColorField(blank=True, null=True, verbose_name='Background color')),
+ ('category', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='serviceform.Level1Category', verbose_name='Level 1 category')),
+ ],
+ options={
+ 'verbose_name': 'Level 2 category',
+ 'verbose_name_plural': 'Level 2 categories',
+ 'ordering': ('order',),
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Participant',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('forenames', models.CharField(max_length=64, verbose_name='Forename(s)')),
+ ('surname', models.CharField(max_length=64, verbose_name='Surname')),
+ ('street_address', models.CharField(max_length=128, verbose_name='Street address')),
+ ('postal_code', models.CharField(max_length=32, validators=[django.core.validators.RegexValidator(code='invalid', message='Enter a valid postal code.', regex='^\\d{5}$')], verbose_name='Zip/Postal code')),
+ ('city', models.CharField(max_length=32, verbose_name='City')),
+ ('email', models.EmailField(db_index=True, max_length=254, verbose_name='Email')),
+ ('phone_number', models.CharField(max_length=32, validators=[django.core.validators.RegexValidator(message="Phone number must be entered in the format: '050123123' or '+35850123123'. Up to 15 digits allowed.", regex='^\\+?1?\\d{9,15}$')], verbose_name='Phone number')),
+ ('auth_keys_hash_storage', django.contrib.postgres.fields.jsonb.JSONField(default=[])),
+ ('secret_key', models.CharField(db_index=True, default=generate_uuid, max_length=36, unique=True, verbose_name='Secret key')),
+ ('year_of_birth', models.SmallIntegerField(blank=True, null=True, verbose_name='Year of birth')),
+ ('status', models.CharField(choices=[('invited', 'invited'), ('ongoing', 'ongoing'), ('updating', 'updating'), ('finished', 'finished')], default='ongoing', max_length=16)),
+ ('last_finished_view', models.CharField(default='', max_length=32)),
+ ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')),
+ ('last_modified', models.DateTimeField(auto_now=True, verbose_name='Last modified')),
+ ('last_finished', models.DateTimeField(null=True, verbose_name='Last finished')),
+ ('email_verified', models.BooleanField(default=False, verbose_name='Email verified')),
+ ('send_email_allowed', models.BooleanField(default=True, help_text='You will receive email that contains a link that allows later modification of the form. Also when new version of form is published, you will be notified. It is highly recommended that you keep this enabled unless you move away and do not want to participate at all any more. You can also change this setting later if you wish.', verbose_name='Sending email allowed')),
+ ('form_revision', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='serviceform.FormRevision')),
+ ],
+ options={
+ 'verbose_name': 'Participant',
+ 'verbose_name_plural': 'Participants',
+ },
+ ),
+ migrations.CreateModel(
+ name='ParticipantLog',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('writer_id', models.PositiveIntegerField()),
+ ('message', models.TextField()),
+ ('participant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.Participant')),
+ ('writer_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='ParticipationActivity',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('additional_info', models.CharField(blank=True, max_length=1024, null=True)),
+ ('created_at', models.DateTimeField(auto_now_add=True, null=True)),
+ ('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.Activity')),
+ ('participant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.Participant')),
+ ],
+ options={
+ 'ordering': ('activity__category__category__order', 'activity__category__order', 'activity__order'),
+ },
+ ),
+ migrations.CreateModel(
+ name='ParticipationActivityChoice',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('additional_info', models.CharField(blank=True, max_length=1024, null=True)),
+ ('created_at', models.DateTimeField(auto_now_add=True, null=True)),
+ ('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='choices_set', to='serviceform.ParticipationActivity')),
+ ('activity_choice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.ActivityChoice')),
+ ],
+ options={
+ 'ordering': ('activity_choice__order',),
+ },
+ ),
+ migrations.CreateModel(
+ name='Question',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('order', models.PositiveIntegerField(db_index=True, default=0, verbose_name='Order')),
+ ('question', models.CharField(max_length=1024, verbose_name='Question')),
+ ('answer_type', models.CharField(choices=[('integer', 'Integer'), ('short_text', 'Short text'), ('long_text', 'Long text'), ('boolean', 'Boolean'), ('date', 'Date')], default='short_text', max_length=16, verbose_name='Answer type')),
+ ('required', models.BooleanField(default=False, verbose_name='Answer required?')),
+ ],
+ options={
+ 'verbose_name': 'Question',
+ 'verbose_name_plural': 'Questions',
+ 'ordering': ('order',),
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='QuestionAnswer',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('answer', models.TextField()),
+ ('created_at', models.DateTimeField(auto_now_add=True, null=True)),
+ ('participant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.Participant')),
+ ('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.Question')),
+ ],
+ options={
+ 'ordering': ('question__order',),
+ },
+ ),
+ migrations.CreateModel(
+ name='ResponsibilityPerson',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('forenames', models.CharField(max_length=64, verbose_name='Forename(s)')),
+ ('surname', models.CharField(max_length=64, verbose_name='Surname')),
+ ('street_address', models.CharField(blank=True, max_length=128, verbose_name='Street address')),
+ ('postal_code', models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator(code='invalid', message='Enter a valid postal code.', regex='^\\d{5}$')], verbose_name='Zip/Postal code')),
+ ('city', models.CharField(blank=True, max_length=32, verbose_name='City')),
+ ('email', models.EmailField(db_index=True, max_length=254, verbose_name='Email')),
+ ('phone_number', models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator(message="Phone number must be entered in the format: '050123123' or '+35850123123'. Up to 15 digits allowed.", regex='^\\+?1?\\d{9,15}$')], verbose_name='Phone number')),
+ ('auth_keys_hash_storage', django.contrib.postgres.fields.jsonb.JSONField(default=[])),
+ ('secret_key', models.CharField(db_index=True, default=generate_uuid, max_length=36, unique=True, verbose_name='Secret key')),
+ ('send_email_notifications', models.BooleanField(default=True, help_text='Send email notifications whenever new participation to administered activities is registered. Email contains also has a link that allows accessing raport of administered activities.', verbose_name='Send email notifications')),
+ ('hide_contact_details', models.BooleanField(default=False, verbose_name='Hide contact details in form')),
+ ('show_full_report', models.BooleanField(default=False, verbose_name='Grant access to full reports')),
+ ],
+ options={
+ 'verbose_name': 'Responsibility person',
+ 'verbose_name_plural': 'Responsibility persons',
+ 'ordering': ('surname',),
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='ServiceForm',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=256, verbose_name='Name of the serviceform')),
+ ('slug', models.SlugField(help_text='Tämä on osa lomakkeen osoitetta, ts. lomakkeen osoitteeksi tulee http://localhost:8000/valitsemasi-nimi-urlissa', unique=True, verbose_name='Slug')),
+ ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')),
+ ('last_updated', models.DateTimeField(auto_now=True, null=True, verbose_name='Last updated')),
+ ('require_email_verification', models.BooleanField(default=True, verbose_name='Require email verification')),
+ ('password', models.CharField(blank=True, default='', help_text='Password that is asked from participants', max_length=32, verbose_name='Password')),
+ ('hide_contact_details', models.BooleanField(default=False, verbose_name='Hide contact details (other than email) in form')),
+ ('flow_by_categories', models.BooleanField(default=False, help_text='Please note that preview shows full form despite this option', verbose_name='Split participation form to level 1 categories')),
+ ('allow_skipping_categories', models.BooleanField(default=False, help_text='In effect only if flow by categories option is enabled. If this option is enabled, user can jump between categories. If disabled, he must proceed them one by one.', verbose_name='Allow jumping between categories')),
+ ('level1_color', ColorField(blank=True, help_text='If left blank (black), default coloring will be used', null=True, verbose_name='Level 1 category default background color')),
+ ('level2_color', ColorField(blank=True, help_text='If left blank (black), it will be derived from level 1 background color', null=True, verbose_name='Level 2 category default background color')),
+ ('activity_color', ColorField(blank=True, help_text='If left blank (black), it will be derived from level 2 background color', null=True, verbose_name='Activity default background color')),
+ ('description', models.TextField(blank=True, help_text='Description box will be shown before instruction box in participation view.', verbose_name='Description')),
+ ('instructions', models.TextField(blank=True, help_text='Use HTML formatting. Leave this empty to use default. This is shown in participation view.', null=True, verbose_name='Instructions')),
+ ('login_text', models.TextField(blank=True, help_text='This will be shown in the login screen', null=True, verbose_name='Login text')),
+ ('required_year_of_birth', models.BooleanField(default=False, verbose_name='Year of birth')),
+ ('required_street_address', models.BooleanField(default=True, verbose_name='Street address')),
+ ('required_phone_number', models.BooleanField(default=True, verbose_name='Phone number')),
+ ('visible_year_of_birth', models.BooleanField(default=True, verbose_name='Year of birth')),
+ ('visible_street_address', models.BooleanField(default=True, verbose_name='Street address')),
+ ('visible_phone_number', models.BooleanField(default=True, verbose_name='Phone number')),
+ ('bulk_email_to_responsibles', models.ForeignKey(blank=True, help_text='Email that is sent to responsibles when emailing starts', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Bulk email to responsibles')),
+ ('current_revision', models.ForeignKey(blank=True, help_text='You need to first add a revision to form (see below) and save. Then newly created revision will appear in the list.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='serviceform.FormRevision', verbose_name='Current revision')),
+ ('email_to_former_participants', models.ForeignKey(blank=True, help_text='Email that is sent to former participants when form is published', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Bulk email to former participants')),
+ ('email_to_invited_users', models.ForeignKey(blank=True, help_text='Email that is sent when user is invited to the form manually via invite form', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Invite email')),
+ ('email_to_participant', models.ForeignKey(blank=True, help_text='Email that is sent to participant after he has fulfilled his participation', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Email to participant, on finish')),
+ ('email_to_participant_on_update', models.ForeignKey(blank=True, help_text='Email that is sent to participant after he has updated his participation', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Email to participant, on update')),
+ ('email_to_responsible_auth_link', models.ForeignKey(blank=True, help_text='Email that is sent to responsible when he requests auth link', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Responsible requests auth link')),
+ ('email_to_responsibles', models.ForeignKey(blank=True, help_text='Email that is sent to responsibles when new participation is registered', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Email to responsibles')),
+ ('last_editor', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='last_edited_serviceform', to=settings.AUTH_USER_MODEL, verbose_name='Last edited by')),
+ ('resend_email_to_participant', models.ForeignKey(blank=True, help_text='Email that is sent to participant if he requests resending email', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Resend email to participant')),
+ ('responsible', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='serviceform.ResponsibilityPerson', verbose_name='Responsible')),
+ ('verification_email_to_participant', models.ForeignKey(blank=True, help_text='Email verification message that is sent to participant when filling form, if email verification is enabled', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Verification email to participant')),
+ ],
+ options={
+ 'verbose_name': 'Service form',
+ 'verbose_name_plural': 'Service forms',
+ },
+ bases=(models.Model,),
+ ),
+ migrations.AddField(
+ model_name='responsibilityperson',
+ name='form',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='serviceform.ServiceForm'),
+ ),
+ migrations.AddField(
+ model_name='question',
+ name='form',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.ServiceForm'),
+ ),
+ migrations.AddField(
+ model_name='question',
+ name='responsibles',
+ field=select2.fields.ManyToManyField(blank=True, related_name='question_related', sorted=False, to='serviceform.ResponsibilityPerson', verbose_name='Responsible persons'),
+ ),
+ migrations.AddField(
+ model_name='level2category',
+ name='responsibles',
+ field=select2.fields.ManyToManyField(blank=True, related_name='level2category_related', sorted=False, to='serviceform.ResponsibilityPerson', verbose_name='Responsible persons'),
+ ),
+ migrations.AddField(
+ model_name='level1category',
+ name='form',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.ServiceForm'),
+ ),
+ migrations.AddField(
+ model_name='level1category',
+ name='responsibles',
+ field=select2.fields.ManyToManyField(blank=True, related_name='level1category_related', sorted=False, to='serviceform.ResponsibilityPerson', verbose_name='Responsible persons'),
+ ),
+ migrations.AddField(
+ model_name='formrevision',
+ name='form',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.ServiceForm', verbose_name='Service form'),
+ ),
+ migrations.AddField(
+ model_name='emailtemplate',
+ name='form',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.ServiceForm'),
+ ),
+ migrations.AddField(
+ model_name='emailmessage',
+ name='template',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='serviceform.EmailTemplate'),
+ ),
+ migrations.AddField(
+ model_name='activitychoice',
+ name='responsibles',
+ field=select2.fields.ManyToManyField(blank=True, related_name='activitychoice_related', sorted=False, to='serviceform.ResponsibilityPerson', verbose_name='Responsible persons'),
+ ),
+ migrations.AddField(
+ model_name='activity',
+ name='category',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serviceform.Level2Category', verbose_name='Category'),
+ ),
+ migrations.AddField(
+ model_name='activity',
+ name='responsibles',
+ field=select2.fields.ManyToManyField(blank=True, related_name='activity_related', sorted=False, to='serviceform.ResponsibilityPerson', verbose_name='Responsible persons'),
+ ),
+ migrations.AlterUniqueTogether(
+ name='participationactivitychoice',
+ unique_together=set([('activity', 'activity_choice')]),
+ ),
+ migrations.AlterUniqueTogether(
+ name='participationactivity',
+ unique_together=set([('participant', 'activity')]),
+ ),
+ migrations.AlterUniqueTogether(
+ name='formrevision',
+ unique_together=set([('form', 'name')]),
+ ),
+ migrations.RunPython(add_basic_rights_groups_and_permissions, null),
+
+ ]
diff --git a/serviceform/serviceform/migrations_/0002_rename_responsibilityperson_to_member.py b/serviceform/serviceform/migrations_/0002_rename_responsibilityperson_to_member.py
new file mode 100644
index 0000000..cceb172
--- /dev/null
+++ b/serviceform/serviceform/migrations_/0002_rename_responsibilityperson_to_member.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2017-05-24 11:51
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('serviceform', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.RenameModel('ResponsibilityPerson', 'Member')
+ ]
diff --git a/serviceform/serviceform/migrations_/0003_auto_20170524_1501.py b/serviceform/serviceform/migrations_/0003_auto_20170524_1501.py
new file mode 100644
index 0000000..2ff2e76
--- /dev/null
+++ b/serviceform/serviceform/migrations_/0003_auto_20170524_1501.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2017-05-24 12:01
+from __future__ import unicode_literals
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('serviceform', '0002_rename_responsibilityperson_to_member'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='member',
+ options={},
+ ),
+ migrations.AddField(
+ model_name='member',
+ name='membership_type',
+ field=models.CharField(choices=[('external', 'external'), ('normal', 'normal'), ('staff', 'staff')], default='external', max_length=8, verbose_name='Is this person a member of this organization?'),
+ ),
+ migrations.AlterField(
+ model_name='member',
+ name='city',
+ field=models.CharField(max_length=32, verbose_name='City'),
+ ),
+ migrations.AlterField(
+ model_name='member',
+ name='phone_number',
+ field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator(message="Phone number must be entered in the format: '050123123' or '+35850123123'. Up to 15 digits allowed.", regex='^\\+?1?\\d{9,15}$')], verbose_name='Phone number'),
+ ),
+ migrations.AlterField(
+ model_name='member',
+ name='postal_code',
+ field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator(code='invalid', message='Enter a valid postal code.', regex='^\\d{5}$')], verbose_name='Zip/Postal code'),
+ ),
+ migrations.AlterField(
+ model_name='member',
+ name='street_address',
+ field=models.CharField(max_length=128, verbose_name='Street address'),
+ ),
+ ]
diff --git a/serviceform/serviceform/migrations_/0004_rename_participant_to_participation.py b/serviceform/serviceform/migrations_/0004_rename_participant_to_participation.py
new file mode 100644
index 0000000..63915d7
--- /dev/null
+++ b/serviceform/serviceform/migrations_/0004_rename_participant_to_participation.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2017-05-24 12:02
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('serviceform', '0003_auto_20170524_1501'),
+ ]
+
+ operations = [
+ migrations.RenameModel('Participant', 'Participation'),
+ migrations.AlterModelOptions(
+ name='participation',
+ options={'verbose_name': 'Participation', 'verbose_name_plural': 'Participants'},
+ ),
+ ]
diff --git a/serviceform/serviceform/migrations_/0005_auto_20170524_1517.py b/serviceform/serviceform/migrations_/0005_auto_20170524_1517.py
new file mode 100644
index 0000000..f7859ba
--- /dev/null
+++ b/serviceform/serviceform/migrations_/0005_auto_20170524_1517.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2017-05-24 12:17
+from __future__ import unicode_literals
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('serviceform', '0004_rename_participant_to_participation'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='member',
+ name='city',
+ field=models.CharField(blank=True, max_length=32, verbose_name='City'),
+ ),
+ migrations.AlterField(
+ model_name='member',
+ name='email',
+ field=models.EmailField(blank=True, db_index=True, max_length=254, verbose_name='Email'),
+ ),
+ migrations.AlterField(
+ model_name='member',
+ name='phone_number',
+ field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator(message="Phone number must be entered in the format: '050123123' or '+35850123123'. Up to 15 digits allowed.", regex='^\\+?1?\\d{9,15}$')], verbose_name='Phone number'),
+ ),
+ migrations.AlterField(
+ model_name='member',
+ name='postal_code',
+ field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator(code='invalid', message='Enter a valid postal code.', regex='^\\d{5}$')], verbose_name='Zip/Postal code'),
+ ),
+ migrations.AlterField(
+ model_name='member',
+ name='street_address',
+ field=models.CharField(blank=True, max_length=128, verbose_name='Street address'),
+ ),
+ ]
diff --git a/serviceform/serviceform/migrations_/0006_organization.py b/serviceform/serviceform/migrations_/0006_organization.py
new file mode 100644
index 0000000..10a845f
--- /dev/null
+++ b/serviceform/serviceform/migrations_/0006_organization.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2017-05-24 12:23
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+def create_organizations(apps, schema_editor):
+ ServiceForm = apps.get_model('serviceform', 'ServiceForm')
+ Organization = apps.get_model('serviceform', 'Organization')
+
+ for s in ServiceForm.objects.all():
+ Organization.objects.create(name=s.slug)
+
+
+def null(apps, schema_editor):
+ pass
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('serviceform', '0005_auto_20170524_1517'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Organization',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=64, verbose_name='Organization name')),
+ ],
+ ),
+ migrations.RunPython(create_organizations, null),
+ ]
diff --git a/serviceform/serviceform/migrations_/0007_member_organization.py b/serviceform/serviceform/migrations_/0007_member_organization.py
new file mode 100644
index 0000000..b98ba70
--- /dev/null
+++ b/serviceform/serviceform/migrations_/0007_member_organization.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2017-05-24 12:28
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+def assign_organizations_to_members(apps, schema_editor):
+ Organization = apps.get_model('serviceform', 'Organization')
+ Member = apps.get_model('serviceform', 'Member')
+
+ for m in Member.objects.all():
+ m.organization = Organization.objects.get(name=m.form.slug)
+ m.save()
+
+
+def null(apps, schema_editor):
+ pass
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('serviceform', '0006_organization'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='member',
+ name='organization',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='serviceform.Organization'),
+ ),
+ migrations.RunPython(assign_organizations_to_members, null),
+
+ migrations.AlterField(
+ model_name='member',
+ name='organization',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
+ to='serviceform.Organization'),
+ ),
+ migrations.RemoveField(
+ model_name='member',
+ name='form',
+ ),
+ ]
diff --git a/serviceform/serviceform/migrations_/0008_add_member_and_organization_fields.py b/serviceform/serviceform/migrations_/0008_add_member_and_organization_fields.py
new file mode 100644
index 0000000..03d75a8
--- /dev/null
+++ b/serviceform/serviceform/migrations_/0008_add_member_and_organization_fields.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2017-05-24 12:33
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+def null(*args, **kwargs):
+ pass
+
+
+def fill_serviceform_organization(apps, schema_editor):
+ ServiceForm = apps.get_model('serviceform', 'ServiceForm')
+ Organization = apps.get_model('serviceform', 'Organization')
+
+ for s in ServiceForm.objects.all():
+ s.organization = Organization.objects.get(name=s.slug)
+ s.save()
+
+
+def fill_participation_member(apps, schema_editor):
+ Participation = apps.get_model('serviceform', 'Participation')
+ Member = apps.get_model('serviceform', 'Member')
+
+ for p in Participation.objects.all():
+ m = Member.objects.filter(email=p.email).first()
+ if not m:
+ m = Member()
+ m.forenames = p.forenames
+ m.surname = p.surname
+ m.street_address = p.street_address
+ m.postal_code = p.postal_code
+ m.city = p.city
+ m.email = p.email
+ m.phone_number = p.phone_number
+
+ m.membership_type = 'external'
+ m.organization = p.form_revision.form.organization
+ m.allow_participant_email = p.send_email_allowed
+
+ m.email_verified = p.email_verified
+ if p.year_of_birth:
+ m.year_of_birth = p.year_of_birth
+
+ for a in p.auth_keys_hash_storage:
+ m.auth_keys_hash_storage.append(a)
+
+ m.auth_keys_hash_storage.sort(key=lambda x: x[1])
+
+ m.save()
+
+ p.member = m
+ p.save()
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('serviceform', '0007_member_organization'),
+ ]
+
+ operations = [
+
+ migrations.AddField(
+ model_name='participation',
+ name='member',
+ field=models.ForeignKey(blank=True, null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ to='serviceform.Member'),
+ ),
+ migrations.AddField(
+ model_name='serviceform',
+ name='organization',
+ field=models.ForeignKey(default=None, null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ to='serviceform.Organization'),
+ ),
+ migrations.AddField(
+ model_name='member',
+ name='email_verified',
+ field=models.BooleanField(default=False, verbose_name='Email verified'),
+ ),
+ migrations.AddField(
+ model_name='member',
+ name='year_of_birth',
+ field=models.SmallIntegerField(blank=True, null=True, verbose_name='Year of birth'),
+ ),
+ migrations.RenameField(
+ model_name='member',
+ old_name='send_email_notifications',
+ new_name='allow_responsible_email',
+ ),
+ migrations.AddField(
+ model_name='member',
+ name='allow_participant_email',
+ field=models.BooleanField(default=True,
+ help_text='You will receive email that contains a link that allows later modification of the form. Also when new version of form is published, you will be notified. It is highly recommended that you keep this enabled unless you move away and do not want to participate at all any more. You can also change this setting later if you wish.',
+ verbose_name='Send email notifications'),
+ ),
+ migrations.RunPython(fill_serviceform_organization, null),
+ migrations.RunPython(fill_participation_member, null),
+ migrations.AlterField(
+ model_name='participation',
+ name='member',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
+ to='serviceform.Member'),
+ ),
+ migrations.AlterField(
+ model_name='serviceform',
+ name='organization',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
+ to='serviceform.Organization'),
+ ),
+ ]
diff --git a/serviceform/serviceform/migrations_/0009_remove_participation_contact_detail_fields.py b/serviceform/serviceform/migrations_/0009_remove_participation_contact_detail_fields.py
new file mode 100644
index 0000000..5c9948a
--- /dev/null
+++ b/serviceform/serviceform/migrations_/0009_remove_participation_contact_detail_fields.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2017-05-24 14:50
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('serviceform', '0008_add_member_and_organization_fields'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='participation',
+ name='city',
+ ),
+ migrations.RemoveField(
+ model_name='participation',
+ name='email',
+ ),
+ migrations.RemoveField(
+ model_name='participation',
+ name='email_verified',
+ ),
+ migrations.RemoveField(
+ model_name='participation',
+ name='forenames',
+ ),
+ migrations.RemoveField(
+ model_name='participation',
+ name='phone_number',
+ ),
+ migrations.RemoveField(
+ model_name='participation',
+ name='postal_code',
+ ),
+ migrations.RemoveField(
+ model_name='participation',
+ name='street_address',
+ ),
+ migrations.RemoveField(
+ model_name='participation',
+ name='surname',
+ ),
+ migrations.RemoveField(
+ model_name='participation',
+ name='year_of_birth',
+ ),
+ migrations.AlterModelOptions(
+ name='participation',
+ options={'verbose_name': 'Participation', 'verbose_name_plural': 'Participations'},
+ ),
+ migrations.RemoveField(
+ model_name='participation',
+ name='auth_keys_hash_storage',
+ ),
+ migrations.RemoveField(
+ model_name='participation',
+ name='secret_key',
+ ),
+ migrations.RemoveField(
+ model_name='participation',
+ name='send_email_allowed',
+ ),
+ ]
diff --git a/serviceform/serviceform/migrations_/0010_rename_member_related_fields.py b/serviceform/serviceform/migrations_/0010_rename_member_related_fields.py
new file mode 100644
index 0000000..4763336
--- /dev/null
+++ b/serviceform/serviceform/migrations_/0010_rename_member_related_fields.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2017-05-25 08:47
+from __future__ import unicode_literals
+
+from django.db import migrations
+import select2.fields
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('serviceform', '0009_remove_participation_contact_detail_fields'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='activity',
+ name='responsibles',
+ field=select2.fields.ManyToManyField(blank=True, related_name='activity_responsibles', sorted=False, to='serviceform.Member', verbose_name='Responsible persons'),
+ ),
+ migrations.AlterField(
+ model_name='activitychoice',
+ name='responsibles',
+ field=select2.fields.ManyToManyField(blank=True, related_name='activitychoice_responsibles', sorted=False, to='serviceform.Member', verbose_name='Responsible persons'),
+ ),
+ migrations.AlterField(
+ model_name='level1category',
+ name='responsibles',
+ field=select2.fields.ManyToManyField(blank=True, related_name='level1category_responsibles', sorted=False, to='serviceform.Member', verbose_name='Responsible persons'),
+ ),
+ migrations.AlterField(
+ model_name='level2category',
+ name='responsibles',
+ field=select2.fields.ManyToManyField(blank=True, related_name='level2category_responsibles', sorted=False, to='serviceform.Member', verbose_name='Responsible persons'),
+ ),
+ migrations.AlterField(
+ model_name='question',
+ name='responsibles',
+ field=select2.fields.ManyToManyField(blank=True, related_name='question_responsibles', sorted=False, to='serviceform.Member', verbose_name='Responsible persons'),
+ ),
+ ]
diff --git a/serviceform/serviceform/migrations_/0011_auto_20170526_1315.py b/serviceform/serviceform/migrations_/0011_auto_20170526_1315.py
new file mode 100644
index 0000000..f79194d
--- /dev/null
+++ b/serviceform/serviceform/migrations_/0011_auto_20170526_1315.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2017-05-26 10:15
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import select2.fields
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('serviceform', '0010_rename_member_related_fields'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='serviceform',
+ name='order',
+ field=models.PositiveIntegerField(db_index=True, default=0, verbose_name='Order'),
+ ),
+ migrations.AddField(
+ model_name='serviceform',
+ name='responsibles',
+ field=select2.fields.ManyToManyField(blank=True, related_name='serviceform_responsibles', sorted=False, to='serviceform.Member', verbose_name='Responsible persons'),
+ ),
+ ]
diff --git a/serviceform/serviceform/migrations_/0012_auto_20170526_1322.py b/serviceform/serviceform/migrations_/0012_auto_20170526_1322.py
new file mode 100644
index 0000000..6f1c378
--- /dev/null
+++ b/serviceform/serviceform/migrations_/0012_auto_20170526_1322.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2017-05-26 10:22
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('serviceform', '0011_auto_20170526_1315'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='level1category',
+ name='skip_numbering',
+ field=models.BooleanField(default=False, verbose_name='Skip'),
+ ),
+ migrations.AddField(
+ model_name='level2category',
+ name='skip_numbering',
+ field=models.BooleanField(default=False, verbose_name='Skip'),
+ ),
+ migrations.AddField(
+ model_name='question',
+ name='skip_numbering',
+ field=models.BooleanField(default=False, verbose_name='Skip'),
+ ),
+ migrations.AddField(
+ model_name='serviceform',
+ name='skip_numbering',
+ field=models.BooleanField(default=False, verbose_name='Skip'),
+ ),
+ ]
diff --git a/serviceform/serviceform/migrations_/0013_emailtemplate_organization.py b/serviceform/serviceform/migrations_/0013_emailtemplate_organization.py
new file mode 100644
index 0000000..b9f435e
--- /dev/null
+++ b/serviceform/serviceform/migrations_/0013_emailtemplate_organization.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2017-06-11 09:49
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+def organization_to_emailtemplates(apps, schema_editor):
+ EmailTemplate = apps.get_model('serviceform', 'EmailTemplate')
+
+ for et in EmailTemplate.objects.all():
+ et.organization = et.form.organization
+ et.name = f'{et.name} ({et.form.name})'
+ et.save()
+
+
+def null(apps, schema_editor):
+ pass
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('serviceform', '0012_auto_20170526_1322'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='emailtemplate',
+ name='organization',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='serviceform.Organization'),
+ ),
+ migrations.RunPython(organization_to_emailtemplates, null)
+ ]
diff --git a/serviceform/serviceform/migrations_/0014_remove_emailtemplate_form.py b/serviceform/serviceform/migrations_/0014_remove_emailtemplate_form.py
new file mode 100644
index 0000000..51c474e
--- /dev/null
+++ b/serviceform/serviceform/migrations_/0014_remove_emailtemplate_form.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2017-06-11 09:55
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('serviceform', '0013_emailtemplate_organization'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='emailtemplate',
+ name='form',
+ ),
+ ]
diff --git a/serviceform/serviceform/migrations_/0015_organization_email_to_member_auth_link.py b/serviceform/serviceform/migrations_/0015_organization_email_to_member_auth_link.py
new file mode 100644
index 0000000..28a8262
--- /dev/null
+++ b/serviceform/serviceform/migrations_/0015_organization_email_to_member_auth_link.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2017-06-11 13:03
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('serviceform', '0014_remove_emailtemplate_form'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='organization',
+ name='email_to_member_auth_link',
+ field=models.ForeignKey(blank=True, help_text='Email that is sent to member when auth link is requested', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Auth link email to member'),
+ ),
+ ]
diff --git a/serviceform/serviceform/migrations_/0016_auto_20170630_1107.py b/serviceform/serviceform/migrations_/0016_auto_20170630_1107.py
new file mode 100644
index 0000000..f75fe02
--- /dev/null
+++ b/serviceform/serviceform/migrations_/0016_auto_20170630_1107.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.2 on 2017-06-30 08:07
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('serviceform', '0015_organization_email_to_member_auth_link'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='question',
+ name='description',
+ field=models.TextField(blank=True, verbose_name='Description'),
+ ),
+ migrations.AddField(
+ model_name='question',
+ name='name',
+ field=models.CharField(default='-', max_length=256, verbose_name='Name'),
+ preserve_default=False,
+ ),
+ ]
diff --git a/serviceform/serviceform/migrations_/0017_auto_20170707_0955.py b/serviceform/serviceform/migrations_/0017_auto_20170707_0955.py
new file mode 100644
index 0000000..d9b088f
--- /dev/null
+++ b/serviceform/serviceform/migrations_/0017_auto_20170707_0955.py
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.3 on 2017-07-07 06:55
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('serviceform', '0016_auto_20170630_1107'),
+ ]
+
+ operations = [
+ migrations.RenameModel(
+ old_name='ParticipantLog',
+ new_name='ParticipationLog',
+ ),
+ migrations.RenameField(
+ model_name='member',
+ old_name='allow_participant_email',
+ new_name='allow_participation_email',
+ ),
+ migrations.RenameField(
+ model_name='participationactivity',
+ old_name='participant',
+ new_name='participation',
+ ),
+ migrations.RenameField(
+ model_name='participationlog',
+ old_name='participant',
+ new_name='participation',
+ ),
+ migrations.RenameField(
+ model_name='questionanswer',
+ old_name='participant',
+ new_name='participation',
+ ),
+ migrations.RemoveField(
+ model_name='formrevision',
+ name='send_bulk_email_to_participants',
+ ),
+ migrations.RemoveField(
+ model_name='serviceform',
+ name='email_to_former_participants',
+ ),
+ migrations.RemoveField(
+ model_name='serviceform',
+ name='email_to_participant',
+ ),
+ migrations.RemoveField(
+ model_name='serviceform',
+ name='email_to_participant_on_update',
+ ),
+ migrations.RemoveField(
+ model_name='serviceform',
+ name='resend_email_to_participant',
+ ),
+ migrations.RemoveField(
+ model_name='serviceform',
+ name='verification_email_to_participant',
+ ),
+ migrations.AddField(
+ model_name='formrevision',
+ name='send_bulk_email_to_participations',
+ field=models.BooleanField(default=True, help_text='Send email to participations that filled the form when this revision was active. Email is sent when new current revision is published.', verbose_name='Send bulk email to participations'),
+ ),
+ migrations.AddField(
+ model_name='serviceform',
+ name='email_to_former_participations',
+ field=models.ForeignKey(blank=True, help_text='Email that is sent to former participations when form is published', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Bulk email to former participations'),
+ ),
+ migrations.AddField(
+ model_name='serviceform',
+ name='email_to_participation',
+ field=models.ForeignKey(blank=True, help_text='Email that is sent to participation after he has fulfilled his participation', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Email to participation, on finish'),
+ ),
+ migrations.AddField(
+ model_name='serviceform',
+ name='email_to_participation_on_update',
+ field=models.ForeignKey(blank=True, help_text='Email that is sent to participation after he has updated his participation', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Email to participation, on update'),
+ ),
+ migrations.AddField(
+ model_name='serviceform',
+ name='resend_email_to_participation',
+ field=models.ForeignKey(blank=True, help_text='Email that is sent to participation if he requests resending email', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Resend email to participation'),
+ ),
+ migrations.AddField(
+ model_name='serviceform',
+ name='verification_email_to_participation',
+ field=models.ForeignKey(blank=True, help_text='Email verification message that is sent to participation when filling form, if email verification is enabled', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='serviceform.EmailTemplate', verbose_name='Verification email to participation'),
+ ),
+ migrations.AlterField(
+ model_name='emailtemplate',
+ name='content',
+ field=models.TextField(help_text='Following context may (depending on topic) be available for both subject and content: {{responsible}}, {{participation}}, {{last_modified}}, {{form}}, {{url}}, {{contact}}', verbose_name='Content'),
+ ),
+ migrations.AlterField(
+ model_name='serviceform',
+ name='password',
+ field=models.CharField(blank=True, default='', help_text='Password that is asked from participations', max_length=32, verbose_name='Password'),
+ ),
+ migrations.AlterUniqueTogether(
+ name='participationactivity',
+ unique_together=set([('participation', 'activity')]),
+ ),
+ ]
diff --git a/serviceform/serviceform/migrations_/__init__.py b/serviceform/serviceform/migrations_/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/serviceform/serviceform/models/__init__.py b/serviceform/serviceform/models/__init__.py
index af1d350..7e702f6 100644
--- a/serviceform/serviceform/models/__init__.py
+++ b/serviceform/serviceform/models/__init__.py
@@ -17,12 +17,9 @@
# along with Serviceform. If not, see .
from .email import EmailMessage, EmailTemplate
-from .participation import (ParticipationActivity, ParticipationActivityChoice, ParticipantLog,
- QuestionAnswer)
-from .people import Participant, ResponsibilityPerson
+from .participation import (ParticipationActivity, ParticipationActivityChoice, ParticipationLog,
+ QuestionAnswer, Participation)
+from .people import Organization, Member
from .serviceform import (ServiceForm, FormRevision, Activity, ActivityChoice, Level1Category,
- Level2Category, Question, ColorField)
+ Level2Category, Question)
-
-from .mixins import (ContactDetailsMixinEmail, ContactDetailsMixin, CopyMixin, NameDescriptionMixin,
- PasswordMixin, SubitemMixin)
\ No newline at end of file
diff --git a/serviceform/serviceform/models/email.py b/serviceform/serviceform/models/email.py
index 7e1d633..4e2df1d 100644
--- a/serviceform/serviceform/models/email.py
+++ b/serviceform/serviceform/models/email.py
@@ -15,6 +15,7 @@
#
# You should have received a copy of the GNU General Public License
# along with Serviceform. If not, see .
+
import json
import logging
from typing import TYPE_CHECKING
@@ -30,10 +31,10 @@
from raven.contrib.django.raven_compat.models import client
logger = logging.getLogger(__name__)
-from .mixins import CopyMixin
if TYPE_CHECKING:
- from .serviceform import ServiceForm
+ from .people import Organization
+
class EmailMessage(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
@@ -109,7 +110,7 @@ def make(cls, template: 'EmailTemplate', context_dict: dict, address: str,
return msg
-class EmailTemplate(CopyMixin, models.Model):
+class EmailTemplate(models.Model):
class Meta:
verbose_name = _('Email template')
verbose_name_plural = _('Email templates')
@@ -121,9 +122,15 @@ def __str__(self):
subject = models.CharField(_('Subject'), max_length=256)
content = models.TextField(_('Content'), help_text=_(
'Following context may (depending on topic) be available for both subject and content: '
- '{{responsible}}, {{participant}}, {{last_modified}}, {{form}}, {{url}}, {{contact}}'))
- form = models.ForeignKey('serviceform.ServiceForm', on_delete=models.CASCADE)
+ '{{responsible}}, {{participation}}, {{last_modified}}, {{form}}, {{url}}, {{contact}}'))
+
+ organization = models.ForeignKey('serviceform.Organization', null=True, on_delete=models.CASCADE)
+ # TODO: attributes that are named as 'form' should be renamed to 'serviceform'
@classmethod
- def make(cls, name: str, form: 'ServiceForm', content: str, subject: str):
- return cls.objects.create(name=name, form=form, subject=subject, content=content)
\ No newline at end of file
+ def make(cls, name: str, organization: 'Organization', content: str, subject: str) \
+ -> 'EmailTemplate':
+ return cls.objects.create(name=name,
+ organization=organization,
+ subject=subject,
+ content=content)
\ No newline at end of file
diff --git a/serviceform/serviceform/models/mixins.py b/serviceform/serviceform/models/mixins.py
index c19112a..210f861 100644
--- a/serviceform/serviceform/models/mixins.py
+++ b/serviceform/serviceform/models/mixins.py
@@ -15,26 +15,17 @@
#
# You should have received a copy of the GNU General Public License
# along with Serviceform. If not, see .
-import datetime
-import time
-from enum import Enum
+
from typing import TYPE_CHECKING
-from django.conf import settings
-from django.contrib.auth.hashers import make_password, check_password
-from django.contrib.postgres.fields import JSONField
from django.core.validators import RegexValidator
from django.db import models
from django.db.models.options import Options
-from django.urls import reverse
-from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
if TYPE_CHECKING:
- from .people import ResponsibilityPerson
-
-from .. import utils
+ from .people import Member
phone_regex = RegexValidator(regex=r'^\+?1?\d{9,15}$',
message=_("Phone number must be entered in the format: "
@@ -46,149 +37,4 @@
code='invalid',
)
-class ContactDetailsMixin(models.Model):
- class Meta:
- abstract = True
-
- def __str__(self):
- if self.forenames or self.surname:
- return '%s %s' % (self.forenames.title(), self.surname.title())
- else:
- return self.email
-
- @property
- def address(self):
- return ('%s\n%s %s' % (
- self.street_address.title(), self.postal_code, self.city.title())).strip()
-
- forenames = models.CharField(max_length=64, verbose_name=_('Forename(s)'))
- surname = models.CharField(max_length=64, verbose_name=_('Surname'))
- street_address = models.CharField(max_length=128, blank=False,
- verbose_name=_('Street address'))
- postal_code = models.CharField(max_length=32, blank=False,
- verbose_name=_('Zip/Postal code'),
- validators=[postalcode_regex])
- city = models.CharField(max_length=32, blank=False, verbose_name=_('City'))
- email = models.EmailField(blank=False, verbose_name=_('Email'), db_index=True)
- phone_number = models.CharField(max_length=32, validators=[phone_regex], blank=False,
- verbose_name=_('Phone number'))
-
- @property
- def contact_details(self):
- yield _('Name'), '%s %s' % (self.forenames.title(), self.surname.title())
- if self.email:
- yield _('Email'), self.email
- if self.phone_number:
- yield _('Phone number'), self.phone_number
- if self.address:
- yield _('Address'), '\n' + self.address
-
- @property
- def contact_display(self):
- return '\n'.join('%s: %s' % (k, v) for k, v in self.contact_details)
-
-
-class ContactDetailsMixinEmail(ContactDetailsMixin):
- class Meta:
- abstract = True
-
-ContactDetailsMixinEmail._meta.get_field('street_address').blank = True
-ContactDetailsMixinEmail._meta.get_field('postal_code').blank = True
-ContactDetailsMixinEmail._meta.get_field('city').blank = True
-ContactDetailsMixinEmail._meta.get_field('phone_number').blank = True
-
-
-class NameDescriptionMixin(models.Model):
- class Meta:
- abstract = True
-
- def __str__(self):
- return self.name
-
- name = models.CharField(max_length=256, verbose_name=_('Name'))
- description = models.TextField(blank=True, verbose_name=_('Description'))
-
-
-class CopyMixin:
- _meta: Options
- def create_copy(self):
- fr = self.__class__()
- for field in self._meta.fields:
- setattr(fr, field.name, getattr(self, field.name))
- fr.pk = None
- return fr
-
-
-class SubitemMixin(CopyMixin):
- subitem_name: str
- _counter: int
-
- def __init__(self, *args, **kwargs):
- self._responsibles = set()
- super().__init__(*args, **kwargs)
-
- @cached_property
- def sub_items(self):
- return getattr(self, self.subitem_name + '_set').all()
-
- def has_responsible(self, r: 'ResponsibilityPerson') -> bool:
- return r in self._responsibles
-
-
-class PasswordMixin(models.Model):
- """
- New 'password' is generated every time user requests a auth email to be sent
- to him. Password will expire after AUTH_KEY_EXPIRE_DAYS. We will store
- AUTH_STORE_KEYS number of most recent keys in a json storage.
- """
-
- AUTH_VIEW: str
-
- class Meta:
- abstract = True
-
- class PasswordStatus(Enum):
- PASSWORD_EXPIRED = object()
- PASSWORD_OK = True
- PASSWORD_NOK = False
-
- # New style auth link hash
- auth_keys_hash_storage = JSONField(default=[]) # List of (hash, expire) tuples
-
- # TODO: remove this field (as well as views using it) when all users are having new auth_key_hash set up.
- secret_key = models.CharField(max_length=36, default=utils.generate_uuid, db_index=True,
- unique=True,
- verbose_name=_('Secret key'))
-
- def make_new_password(self) -> str:
- valid_hashes = []
- for key, expire in self.auth_keys_hash_storage:
- if expire > time.time():
- valid_hashes.append((key, expire))
-
- password = utils.generate_uuid()
-
- auth_key_hash = make_password(password)
- auth_key_expire: datetime.datetime = (timezone.now() +
- datetime.timedelta(days=getattr(settings, 'AUTH_KEY_EXPIRE_DAYS', 90)))
-
- valid_hashes.append((auth_key_hash, auth_key_expire.timestamp()))
- self.auth_keys_hash_storage = valid_hashes[-getattr(settings, 'AUTH_STORE_KEYS', 10):]
- self.save(update_fields=['auth_keys_hash_storage'])
- return password
-
- def make_new_auth_url(self) -> str:
- url = settings.SERVER_URL + reverse(self.AUTH_VIEW, args=(self.pk,
- self.make_new_password(),))
- return url
-
- def check_auth_key(self, password: str) -> PasswordStatus:
- for key, expire_timestamp in reversed(self.auth_keys_hash_storage):
- if check_password(password, key):
- if expire_timestamp < time.time():
- return self.PasswordStatus.PASSWORD_EXPIRED
- return self.PasswordStatus.PASSWORD_OK
-
- return self.PasswordStatus.PASSWORD_NOK
-
diff --git a/serviceform/serviceform/models/participation.py b/serviceform/serviceform/models/participation.py
index fad8acb..d13e763 100644
--- a/serviceform/serviceform/models/participation.py
+++ b/serviceform/serviceform/models/participation.py
@@ -16,44 +16,278 @@
# You should have received a copy of the GNU General Public License
# along with Serviceform. If not, see .
-from typing import Sequence, TYPE_CHECKING
+from enum import Enum
+from typing import Sequence, TYPE_CHECKING, Union, Iterator, Tuple, List, Optional
+
+from django.contrib import messages
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
+from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
+from django.urls import reverse
+from django.utils import timezone
+from django.utils.formats import localize
from django.utils.functional import cached_property
+from django.utils.translation import ugettext_lazy as _
+
from .. import utils
if TYPE_CHECKING:
- from .people import Participant
+ from .email import EmailMessage
+ from .serviceform import ServiceForm
-class ParticipantLog(models.Model):
- created_at = models.DateTimeField(auto_now_add=True)
- participant = models.ForeignKey('serviceform.Participant', on_delete=models.CASCADE)
- writer_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
- writer_id = models.PositiveIntegerField()
- # Can be either responsible or django user
- written_by = GenericForeignKey('writer_type', 'writer_id')
- message = models.TextField()
+
+class Participation(models.Model):
+ class Meta:
+ verbose_name = _('Participation')
+ verbose_name_plural = _('Participations')
+ # TODO: unique together: member + form?
+
+ # Current view is set by view decorator require_authenticated_participation
+ _current_view = 'contact_details'
+
+ class EmailIds(Enum):
+ ON_FINISH = object()
+ ON_UPDATE = object()
+ NEW_FORM_REVISION = object()
+ RESEND = object()
+ INVITE = object()
+ EMAIL_VERIFICATION = object()
+
+ SEND_ALWAYS_EMAILS = [EmailIds.RESEND,
+ EmailIds.EMAIL_VERIFICATION,
+ EmailIds.ON_FINISH,
+ EmailIds.ON_UPDATE]
+
+ STATUS_INVITED = 'invited'
+ STATUS_ONGOING = 'ongoing'
+ STATUS_UPDATING = 'updating'
+ STATUS_FINISHED = 'finished'
+ READY_STATUSES = (STATUS_UPDATING, STATUS_FINISHED)
+ EDIT_STATUSES = (STATUS_UPDATING, STATUS_ONGOING)
+
+ STATUS_CHOICES = (
+ (STATUS_INVITED, _('invited')),
+ (STATUS_ONGOING, _('ongoing')),
+ (STATUS_UPDATING, _('updating')),
+ (STATUS_FINISHED, _('finished')))
+ STATUS_DICT = dict(STATUS_CHOICES)
+
+ member = models.ForeignKey('serviceform.Member', on_delete=models.CASCADE)
+ status = models.CharField(max_length=16, choices=STATUS_CHOICES, default=STATUS_ONGOING)
+ last_finished_view = models.CharField(max_length=32, default='')
+ created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Created at'))
+ last_modified = models.DateTimeField(auto_now=True, verbose_name=_('Last modified'))
+ last_finished = models.DateTimeField(_('Last finished'), null=True)
+
+ # Last form revision
+ form_revision = models.ForeignKey('serviceform.FormRevision', null=True,
+ on_delete=models.CASCADE)
+
+ @property
+ def is_updating(self) -> bool:
+ return self.status == self.STATUS_UPDATING
+
+ def __str__(self):
+ return f'Participation for {self.member} in {self.form}'
+
+ @property
+ def additional_data(self) -> Iterator[Tuple[str, str]]:
+ yield _('Participation created in system'), self.created_at
+ yield _('Last finished'), self.last_finished
+ yield _('Last modified'), self.last_modified
+ # TODO: these should be to be shown (also) in Member view, when it is created
+ yield _('Email address verified'), (_('No'), _('Yes'))[self.member.email_verified]
+ yield (_('Participation emails allowed'),
+ (_('No'), _('Yes'))[self.member.allow_participation_email])
+ yield _('Form status'), self.STATUS_DICT[self.status]
+
+ @cached_property
+ def item_count(self) -> int:
+ choices = ParticipationActivityChoice.objects.filter(
+ activity__participation=self).values_list('activity_id', flat=True)
+ choice_count = len(choices)
+ activity_count = self.participationactivity_set.exclude(pk__in=choices).count()
+ return activity_count + choice_count
+
+ @cached_property
+ def activities(self) -> 'Sequence[ParticipationActivity]':
+ return self.participationactivity_set.select_related('activity')
+
+ @cached_property
+ def questions(self) -> 'Sequence[QuestionAnswer]':
+ return self.questionanswer_set.select_related('question')
+
+ def activities_display(self) -> str:
+ return ', '.join(a.activity.name for a in self.activities)
+
+ activities_display.short_description = _('Activities')
+
+ @cached_property
+ def form(self) -> 'Optional[ServiceForm]':
+ return self.form_revision.form if self.form_revision else None
+
+ def form_display(self) -> str:
+ return str(self.form)
+
+ form_display.short_description = _('Form')
+
+ def send_email_to_responsibles(self) -> None:
+ """
+ Go through choices, activities, their categories and send email to responsibles.
+
+ :return:
+ """
+ responsibles = set()
+
+ for pa in self.activities:
+ if self.last_finished is None or pa.created_at > self.last_finished:
+ responsibles.update(set(pa.activity.responsibles.all()) |
+ set(pa.activity.category.responsibles.all()) |
+ set(pa.activity.category.category.responsibles.all()))
+ for pc in pa.choices:
+ if self.last_finished is None or pc.created_at > self.last_finished:
+ responsibles.update(set(pc.activity_choice.responsibles.all()))
+
+ for q in self.questionanswer_set.all():
+ if self.last_finished is None or q.created_at > self.last_finished:
+ responsibles.update(set(q.question.responsibles.all()))
+
+ for r in responsibles:
+ r.send_responsibility_email(self)
+
+ def finish(self, email_participation: bool=True) -> None:
+ updating = self.status == self.STATUS_UPDATING
+ self.status = self.STATUS_FINISHED
+ if timezone.now() > self.form_revision.send_emails_after:
+ self.send_email_to_responsibles()
+ if email_participation:
+ self.send_participation_email(
+ self.EmailIds.ON_UPDATE if updating else self.EmailIds.ON_FINISH)
+ self.last_finished = timezone.now()
+ self.save(update_fields=['status', 'last_finished'])
+
+ def send_participation_email(self, event: EmailIds,
+ extra_context: dict=None) -> 'Optional[EmailMessage]':
+ """
+ Send email to participation
+ :return: False if email was not sent. Message if it was sent.
+ """
+ from .email import EmailMessage
+ if not self.member.allow_participation_email and event not in self.SEND_ALWAYS_EMAILS:
+ return
+
+ emailtemplates = {self.EmailIds.ON_FINISH: self.form.email_to_participation,
+ self.EmailIds.ON_UPDATE: self.form.email_to_participation_on_update,
+ self.EmailIds.NEW_FORM_REVISION: self.form.email_to_former_participations,
+ self.EmailIds.RESEND: self.form.resend_email_to_participation,
+ self.EmailIds.INVITE: self.form.email_to_invited_users,
+ self.EmailIds.EMAIL_VERIFICATION:
+ self.form.verification_email_to_participation,
+ }
+
+ emailtemplate = emailtemplates[event]
+ url_postfix = (f"?next={reverse('participation', args=(self.form.slug,))}"
+ if event == self.EmailIds.EMAIL_VERIFICATION
+ else f"?next={reverse('update_participation', args=(self.form.slug,))}")
+ url = self.member.make_new_auth_url() + url_postfix
+
+ context = {
+ 'participation': str(self),
+ 'contact': self.form.responsible.contact_display,
+ 'form': str(self.form),
+ 'url': str(url),
+ 'last_modified': localize(self.last_modified, use_l10n=True),
+ 'list_unsubscribe': self.member.list_unsubscribe_link,
+ }
+ if extra_context:
+ context.update(extra_context)
+ return EmailMessage.make(emailtemplate, context, self.member.email)
+
+ def resend_auth_link(self) -> 'Optional[EmailMessage]':
+ return self.send_participation_email(self.EmailIds.RESEND)
+
+ @property
+ def flow(self) -> List[str]:
+ from ..urls import participation_flow_urls
+
+ rv = [i.name for i in participation_flow_urls]
+ if not self.form.questions:
+ rv.remove('questions')
+ if not self.form.require_email_verification or self.member.email_verified:
+ rv.remove('email_verification')
+ if self.form.require_email_verification and not self.member.email:
+ rv.remove('email_verification')
+ if not self.form.is_published:
+ rv = ['contact_details', 'submitted']
+ return rv
+
+ def can_access_view(self, view_name: str, auth: bool=False) -> bool:
+ """
+ Access is granted to next view after last finished view
+
+ auth: if query is for authentication (if we can already really proceed to view or not).
+ """
+ if view_name == 'submitted' and not auth:
+ return False
+ last = self.flow.index(
+ self.last_finished_view) if self.last_finished_view in self.flow else -1
+ cur = self.flow.index(view_name) if view_name in self.flow else last + 2
+ if self.form.flow_by_categories and self.form.allow_skipping_categories:
+ # In participation view, allow going straight to questions if skipping categories
+ # is allowed
+ if self.form.require_email_verification:
+ if self.last_finished_view == 'email_verification':
+ last += 1
+ elif self.last_finished_view == 'contact_details':
+ last += 1
+
+ return cur <= last + 1
+
+ def proceed_to_view(self, next_view: str) -> None:
+ if not self.can_access_view(next_view):
+ _next = self.flow.index(next_view)
+ self.last_finished_view = self.flow[_next - 1]
+ self.save(update_fields=['last_finished_view'])
+
+ @property
+ def next_view_name(self) -> str:
+ return self.flow[self.flow.index(self._current_view) + 1]
+
+ def redirect_next(self, request: HttpRequest, message: bool=True) -> HttpResponse:
+ if self.status == self.STATUS_UPDATING and message:
+ messages.warning(request, _(
+ 'Updated information has been stored! Please proceed until the end of the form.'))
+ return HttpResponseRedirect(reverse(self.next_view_name, args=(self.form.slug,)))
+
+ def redirect_last(self) -> HttpResponse:
+ last = self.flow.index(
+ self.last_finished_view) if self.last_finished_view in self.flow else -1
+ return HttpResponseRedirect(reverse(self.flow[last + 1], args=(self.form.slug,)))
+
+ @cached_property
+ def log(self) -> 'Sequence[ParticipationLog]':
+ return self.participationlog_set.all()
class ParticipationActivity(models.Model):
class Meta:
- unique_together = (('participant', 'activity'),)
+ unique_together = (('participation', 'activity'),)
ordering = (
'activity__category__category__order', 'activity__category__order', 'activity__order',)
- participant = models.ForeignKey('serviceform.Participant', on_delete=models.CASCADE)
+ participation = models.ForeignKey(Participation, on_delete=models.CASCADE)
activity = models.ForeignKey('serviceform.Activity', on_delete=models.CASCADE)
additional_info = models.CharField(max_length=1024, blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True, null=True)
@cached_property
- def cached_participant(self) -> 'Participant':
- return utils.get_participant(self.participant_id)
+ def cached_participation(self) -> 'Participation':
+ return utils.get_participation(self.participation_id)
def __str__(self):
- return '%s for %s' % (self.activity, self.participant)
+ return '%s for %s' % (self.activity, self.participation)
@property
def choices(self) -> 'Sequence[ParticipationActivityChoice]':
@@ -76,11 +310,11 @@ class Meta:
created_at = models.DateTimeField(auto_now_add=True, null=True)
@cached_property
- def cached_participant(self) -> 'Participant':
- return utils.get_participant(self.activity.participant_id)
+ def cached_participation(self) -> 'Participation':
+ return utils.get_participation(self.activity.participation_id)
def __str__(self):
- return '%s for %s' % (self.activity_choice, self.activity.participant)
+ return '%s for %s' % (self.activity_choice, self.activity.participation)
@property
def additional_info_display(self) -> str:
@@ -88,7 +322,7 @@ def additional_info_display(self) -> str:
class QuestionAnswer(models.Model):
- participant = models.ForeignKey('serviceform.Participant', on_delete=models.CASCADE)
+ participation = models.ForeignKey('serviceform.Participation', on_delete=models.CASCADE)
question = models.ForeignKey('serviceform.Question', on_delete=models.CASCADE)
answer = models.TextField()
created_at = models.DateTimeField(auto_now_add=True, null=True)
@@ -97,8 +331,18 @@ class Meta:
ordering = ('question__order',)
@cached_property
- def cached_participant(self) -> 'Participant':
- return utils.get_participant(self.participant_id)
+ def cached_participation(self) -> 'Participation':
+ return utils.get_participation(self.participation_id)
def __str__(self):
- return '%s: %s' % (self.question.question, self.answer)
\ No newline at end of file
+ return '%s: %s' % (self.question.question, self.answer)
+
+
+class ParticipationLog(models.Model):
+ created_at = models.DateTimeField(auto_now_add=True)
+ participation = models.ForeignKey('serviceform.Participation', on_delete=models.CASCADE)
+ writer_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
+ writer_id = models.PositiveIntegerField()
+ # Can be either responsible or django user
+ written_by = GenericForeignKey('writer_type', 'writer_id')
+ message = models.TextField()
diff --git a/serviceform/serviceform/models/people.py b/serviceform/serviceform/models/people.py
index ba14f2b..792ae86 100644
--- a/serviceform/serviceform/models/people.py
+++ b/serviceform/serviceform/models/people.py
@@ -15,219 +15,218 @@
#
# You should have received a copy of the GNU General Public License
# along with Serviceform. If not, see .
+
from enum import Enum
-from typing import Union, Iterator, Tuple, Optional, List, Sequence, TYPE_CHECKING
+from typing import Optional, TYPE_CHECKING, Union, Iterator, List
+import logging
+
+import time
+import datetime
from django.conf import settings
-from django.contrib import messages
+from django.contrib.auth.hashers import make_password, check_password
+from django.contrib.postgres.fields import JSONField
from django.db import models
-from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
from django.urls import reverse
-from django.utils import timezone
-from django.utils.formats import localize
from django.utils.functional import cached_property
from django.utils.html import format_html
from django.utils.translation import ugettext_lazy as _
+from django.utils import timezone
+
+from .email import EmailMessage, EmailTemplate
+from .mixins import postalcode_regex, phone_regex
+from .. import utils, emails
-from .. import utils
-from .mixins import CopyMixin, PasswordMixin, ContactDetailsMixinEmail, ContactDetailsMixin
-from .email import EmailMessage
if TYPE_CHECKING:
- from .participation import ParticipationActivity, QuestionAnswer, ParticipantLog
+ from .participation import Participation
from .serviceform import ServiceForm
+logger = logging.getLogger(__name__)
-class ResponsibilityPerson(CopyMixin, PasswordMixin, ContactDetailsMixinEmail, models.Model):
- class Meta:
- verbose_name = _('Responsibility person')
- verbose_name_plural = _('Responsibility persons')
- ordering = ('surname',)
- AUTH_VIEW = 'authenticate_responsible_new'
+class Organization(models.Model):
+ name = models.CharField(_('Organization name'), max_length=64)
- form = models.ForeignKey('serviceform.ServiceForm', null=True)
- send_email_notifications = models.BooleanField(
- default=True,
- verbose_name=_('Send email notifications'),
- help_text=_(
- 'Send email notifications whenever new participation to administered activities is '
- 'registered. Email contains also has a link that allows accessing raport of '
- 'administered activities.'))
+ email_to_member_auth_link = models.ForeignKey(
+ EmailTemplate, null=True, blank=True,
+ related_name='+',
+ verbose_name=_('Auth link email to member'), help_text=_(
+ 'Email that is sent to member when auth link is requested'),
+ on_delete=models.SET_NULL)
- hide_contact_details = models.BooleanField(_('Hide contact details in form'), default=False)
- show_full_report = models.BooleanField(_('Grant access to full reports'), default=False)
+ def create_initial_data(self) -> None:
+ self.create_email_templates()
+ self.save()
- @cached_property
- def item_count(self) -> int:
- return utils.count_for_responsible(self)
-
- def personal_link(self) -> str:
- return format_html('{}',
- reverse('authenticate_responsible_mock', args=(self.pk,)),
- self.pk) if self.pk else ''
-
- personal_link.short_description = _('Link to personal report')
-
- @property
- def secret_id(self) -> str:
- return utils.encode(self.id)
-
- @property
- def list_unsubscribe_link(self) -> str:
- return settings.SERVER_URL + reverse('unsubscribe_responsible', args=(self.secret_id,))
+ def create_email_templates(self) -> None:
+ if not self.pk:
+ logger.error('Cannot create email template if form is not saved')
+ return
- def resend_auth_link(self) -> 'EmailMessage':
+ commit = False
+ if not self.email_to_member_auth_link:
+ commit = True
+ self.email_to_member_auth_link = EmailTemplate.make(
+ _('Default auth link to member email'), self,
+ emails.email_to_member_auth_link,
+ _('Your authentication link to access your data in {{organization}}'))
+
+ if commit:
+ self.save()
+
+
+class Member(models.Model):
+ class PasswordStatus(Enum):
+ PASSWORD_EXPIRED = object()
+ PASSWORD_OK = True
+ PASSWORD_NOK = False
+
+ MEMBER_EXTERNAL = 'external'
+ MEMBER_NORMAL = 'normal'
+ MEMBER_STAFF = 'staff'
+ MEMBERSHIP_CHOICES = (
+ (MEMBER_EXTERNAL, _('external')),
+ (MEMBER_NORMAL, _('normal')),
+ (MEMBER_STAFF, _('staff'))
+ )
+
+ forenames = models.CharField(max_length=64, verbose_name=_('Forename(s)'))
+ surname = models.CharField(max_length=64, verbose_name=_('Surname'))
+ street_address = models.CharField(max_length=128, blank=True,
+ verbose_name=_('Street address'))
+ postal_code = models.CharField(max_length=32, blank=True,
+ verbose_name=_('Zip/Postal code'),
+ validators=[postalcode_regex])
+ city = models.CharField(max_length=32, blank=True, verbose_name=_('City'))
- context = {'responsible': str(self),
- 'form': str(self.form),
- 'url': self.make_new_auth_url(),
- 'contact': self.form.responsible.contact_display,
- 'list_unsubscribe': self.list_unsubscribe_link,
- }
- return EmailMessage.make(self.form.email_to_responsible_auth_link, context, self.email)
+ year_of_birth = models.SmallIntegerField(_('Year of birth'), null=True, blank=True)
- def send_responsibility_email(self, participant: 'Participant') -> None:
- if self.send_email_notifications:
- context = {'responsible': str(self),
- 'participant': str(participant),
- 'form': str(self.form),
- 'url': self.make_new_auth_url(),
- 'contact': self.form.responsible.contact_display,
- 'list_unsubscribe': self.list_unsubscribe_link,
- }
+ # TODO: set unique constraint... with the exception if email is not available
+ email = models.EmailField(blank=True, verbose_name=_('Email'), db_index=True)
+ email_verified = models.BooleanField(_('Email verified'), default=False)
- EmailMessage.make(self.form.email_to_responsibles, context, self.email)
+ phone_number = models.CharField(max_length=32, validators=[phone_regex], blank=True,
+ verbose_name=_('Phone number'))
- def send_bulk_mail(self) -> 'Optional[EmailMessage]':
- if self.send_email_notifications:
- context = {'responsible': str(self),
- 'form': str(self.form),
- 'url': self.make_new_auth_url(),
- 'contact': self.form.responsible.contact_display,
- 'list_unsubscribe': self.list_unsubscribe_link,
- }
- return EmailMessage.make(self.form.bulk_email_to_responsibles, context, self.email)
+ membership_type = models.CharField(_('Is this person a member of this organization?'),
+ max_length=8,
+ choices=MEMBERSHIP_CHOICES,
+ default=MEMBER_EXTERNAL)
+ organization = models.ForeignKey(Organization, on_delete=models.CASCADE)
-class Participant(ContactDetailsMixin, PasswordMixin, models.Model):
- email: str
-
- class Meta:
- verbose_name = _('Participant')
- verbose_name_plural = _('Participants')
-
- # Current view is set by view decorator require_authenticated_participant
- _current_view = 'contact_details'
- AUTH_VIEW = 'authenticate_participant_new'
-
- class EmailIds(Enum):
- ON_FINISH = object()
- ON_UPDATE = object()
- NEW_FORM_REVISION = object()
- RESEND = object()
- INVITE = object()
- EMAIL_VERIFICATION = object()
-
- SEND_ALWAYS_EMAILS = [EmailIds.RESEND,
- EmailIds.EMAIL_VERIFICATION,
- EmailIds.ON_FINISH,
- EmailIds.ON_UPDATE]
-
- STATUS_INVITED = 'invited'
- STATUS_ONGOING = 'ongoing'
- STATUS_UPDATING = 'updating'
- STATUS_FINISHED = 'finished'
- READY_STATUSES = (STATUS_UPDATING, STATUS_FINISHED)
- EDIT_STATUSES = (STATUS_UPDATING, STATUS_ONGOING)
-
- STATUS_CHOICES = (
- (STATUS_INVITED, _('invited')),
- (STATUS_ONGOING, _('ongoing')),
- (STATUS_UPDATING, _('updating')),
- (STATUS_FINISHED, _('finished')))
- STATUS_DICT = dict(STATUS_CHOICES)
+ # New style auth link hash
+ auth_keys_hash_storage = JSONField(default=[]) # List of (hash, expire) tuples
- year_of_birth = models.SmallIntegerField(_('Year of birth'), null=True, blank=True)
- status = models.CharField(max_length=16, choices=STATUS_CHOICES, default=STATUS_ONGOING)
- last_finished_view = models.CharField(max_length=32, default='')
- created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Created at'))
- last_modified = models.DateTimeField(auto_now=True, verbose_name=_('Last modified'))
- last_finished = models.DateTimeField(_('Last finished'), null=True)
+ # TODO: remove this field (as well as views using it) when all users are having new auth_key_hash set up.
+ secret_key = models.CharField(max_length=36, default=utils.generate_uuid, db_index=True,
+ unique=True,
+ verbose_name=_('Secret key'))
- # Last form revision
- form_revision = models.ForeignKey('serviceform.FormRevision', null=True, on_delete=models.CASCADE)
- email_verified = models.BooleanField(_('Email verified'), default=False)
+ allow_responsible_email = models.BooleanField(
+ default=True,
+ verbose_name=_('Send email notifications'),
+ help_text=_(
+ 'Send email notifications whenever new participation to administered activities is '
+ 'registered. Email contains also has a link that allows accessing raport of '
+ 'administered activities.'))
- send_email_allowed = models.BooleanField(_('Sending email allowed'), default=True, help_text=_(
+ allow_participation_email = models.BooleanField(
+ default=True,
+ verbose_name=_('Send email notifications'),
+ help_text=_(
'You will receive email that contains a link that allows later modification of the form. '
'Also when new version of form is published, you will be notified. '
'It is highly recommended that you keep this enabled unless you move away '
'and do not want to participate at all any more. You can also change this setting later '
'if you wish.'))
+ # TODO: rename: allow_showing_contact_details_in_forms etc.
+ hide_contact_details = models.BooleanField(_('Hide contact details in form'), default=False)
+ show_full_report = models.BooleanField(_('Grant access to full reports (whole organization)'), default=False)
+
+ @property
+ def secret_id(self) -> str:
+ return utils.encode(self.id)
+
@cached_property
- def age(self) -> Union[int, 'str']:
+ def age(self) -> Union[int, str]:
return timezone.now().year - self.year_of_birth if self.year_of_birth else '-'
- @property
- def is_updating(self) -> bool:
- return self.status == self.STATUS_UPDATING
+ def make_new_password(self) -> str:
+ valid_hashes = []
+ for key, expire in self.auth_keys_hash_storage:
+ if expire > time.time():
+ valid_hashes.append((key, expire))
- @property
- def contact_details(self) -> Iterator[str]:
- yield from super().contact_details
- yield _('Year of birth'), self.year_of_birth or '-'
+ password = utils.generate_uuid()
- @property
- def additional_data(self) -> Iterator[Tuple[str, str]]:
- yield _('Participant created in system'), self.created_at
- yield _('Last finished'), self.last_finished
- yield _('Last modified'), self.last_modified
- yield _('Email address verified'), (_('No'), _('Yes'))[self.email_verified]
- yield _('Emails allowed'), (_('No'), _('Yes'))[self.send_email_allowed]
- yield _('Form status'), self.STATUS_DICT[self.status]
+ auth_key_hash = make_password(password)
+ auth_key_expire: datetime.datetime = (timezone.now() +
+ datetime.timedelta(days=getattr(settings, 'AUTH_KEY_EXPIRE_DAYS', 90)))
- @cached_property
- def item_count(self) -> int:
- from .participation import ParticipationActivityChoice
- choices = ParticipationActivityChoice.objects.filter(
- activity__participant=self).values_list('activity_id', flat=True)
- choice_count = len(choices)
- activity_count = self.participationactivity_set.exclude(pk__in=choices).count()
- return activity_count + choice_count
+ valid_hashes.append((auth_key_hash, auth_key_expire.timestamp()))
+ self.auth_keys_hash_storage = valid_hashes[-getattr(settings, 'AUTH_STORE_KEYS', 10):]
+ self.save(update_fields=['auth_keys_hash_storage'])
+ return password
- def make_new_verification_url(self) -> str:
- return settings.SERVER_URL + reverse('verify_email',
- args=(self.pk, self.make_new_password()))
+ def make_new_auth_url(self) -> str:
+ # TODO: rename view as generic authenticate_member
+ return settings.SERVER_URL + reverse('authenticate_member',
+ args=(self.pk, self.make_new_password(),))
- @cached_property
- def activities(self) -> 'Sequence[ParticipationActivity]':
- return self.participationactivity_set.select_related('activity')
+ def check_auth_key(self, password: str) -> PasswordStatus:
+ for key, expire_timestamp in reversed(self.auth_keys_hash_storage):
+ if check_password(password, key):
+ if expire_timestamp < time.time():
+ return self.PasswordStatus.PASSWORD_EXPIRED
+ return self.PasswordStatus.PASSWORD_OK
- @cached_property
- def questions(self) -> 'Sequence[QuestionAnswer]':
- return self.questionanswer_set.select_related('question')
+ return self.PasswordStatus.PASSWORD_NOK
- def activities_display(self) -> str:
- return ', '.join(a.activity.name for a in self.activities)
+ def __str__(self):
+ if self.forenames or self.surname:
+ return '%s %s' % (self.forenames.title(), self.surname.title())
+ else:
+ return self.email
- activities_display.short_description = _('Activities')
+ @property
+ def member(self) -> 'Member':
+ """Convenience property, to make Participation and Member interfaces
+ similar for templates"""
+ return self
- @cached_property
- def form(self) -> 'ServiceForm':
- return self.form_revision.form if self.form_revision else None
+ @property
+ def address(self):
+ return ('%s\n%s %s' % (
+ self.street_address.title(), self.postal_code, self.city.title())).strip()
+
+
+ @property
+ def contact_details(self) -> Iterator[str]:
+ yield _('Name'), '%s %s' % (self.forenames.title(), self.surname.title())
+ if self.email:
+ yield _('Email'), self.email
+ if self.phone_number:
+ yield _('Phone number'), self.phone_number
+ if self.address:
+ yield _('Address'), '\n' + self.address
+ yield _('Year of birth'), self.year_of_birth or '-'
- def form_display(self) -> str:
- return str(self.form)
+ @property
+ def contact_display(self):
+ return '\n'.join('%s: %s' % (k, v) for k, v in self.contact_details)
- form_display.short_description = _('Form')
+ @cached_property
+ def item_count(self) -> int:
+ return utils.count_for_responsible(self)
def personal_link(self) -> str:
return format_html('{}',
- reverse('authenticate_participant_mock', args=(self.pk,)),
- self.pk)
+ reverse('authenticate_mock', args=(self.pk,)),
+ self.pk) if self.pk else ''
personal_link.short_description = _('Link to personal report')
@@ -235,142 +234,51 @@ def personal_link(self) -> str:
def secret_id(self) -> str:
return utils.encode(self.id)
+ # TODO: common unsubscribe -- rename view
@property
def list_unsubscribe_link(self) -> str:
- return settings.SERVER_URL + reverse('unsubscribe_participant', args=(self.secret_id,))
+ return settings.SERVER_URL + reverse('unsubscribe_member', args=(self.secret_id,))
- def send_email_to_responsibles(self) -> None:
- """
- Go through choices, activities, their categories and send email to responsibles.
+ # TODO: rename to 'send_auth_link'
+ def resend_auth_link(self) -> 'EmailMessage':
+ context = {'member': str(self), # TODO: check context (responsible -> member)
+ 'url': self.make_new_auth_url(),
+ # TODO we might need organization contact details here?
+ #'contact': self.form.responsible.contact_display,
+ 'list_unsubscribe': self.list_unsubscribe_link,
+ }
+ # TODO: more generic auth link email to organization member (not responsible nor participation)
+ # TODO auth link email should be per-organization, not per-form. Members will be shared between forms.
+ return EmailMessage.make(self.organization.email_to_member_auth_link, context, self.email)
- :return:
- """
- responsibles = set()
-
- for pa in self.activities:
- if self.last_finished is None or pa.created_at > self.last_finished:
- responsibles.update(set(pa.activity.responsibles.all()) |
- set(pa.activity.category.responsibles.all()) |
- set(pa.activity.category.category.responsibles.all()))
- for pc in pa.choices:
- if self.last_finished is None or pc.created_at > self.last_finished:
- responsibles.update(set(pc.activity_choice.responsibles.all()))
-
- for q in self.questionanswer_set.all():
- if self.last_finished is None or q.created_at > self.last_finished:
- responsibles.update(set(q.question.responsibles.all()))
-
- for r in responsibles:
- r.send_responsibility_email(self)
-
- def finish(self, email_participant: bool=True) -> None:
- updating = self.status == self.STATUS_UPDATING
- self.status = self.STATUS_FINISHED
- if timezone.now() > self.form_revision.send_emails_after:
- self.send_email_to_responsibles()
- if email_participant:
- self.send_participant_email(
- self.EmailIds.ON_UPDATE if updating else self.EmailIds.ON_FINISH)
- self.last_finished = timezone.now()
- self.save(update_fields=['status', 'last_finished'])
-
- def send_participant_email(self, event: EmailIds,
- extra_context: dict=None) -> 'Optional[EmailMessage]':
- """
- Send email to participant
- :return: False if email was not sent. Message if it was sent.
- """
- if not self.send_email_allowed and event not in self.SEND_ALWAYS_EMAILS:
- return
+ def send_responsibility_email(self, participation: 'Participation') -> None:
+ if self.allow_responsible_email:
+ next = reverse('responsible_report', args=(participation.form.slug,))
+ context = {'responsible': str(self),
+ 'participation': str(participation),
+ 'form': str(participation.form),
+ 'url': self.make_new_auth_url() + f'?next={next}',
+ 'contact': participation.form.responsible.contact_display,
+ 'list_unsubscribe': self.list_unsubscribe_link,
+ }
- self.form.create_email_templates()
-
- emailtemplates = {self.EmailIds.ON_FINISH: self.form.email_to_participant,
- self.EmailIds.ON_UPDATE: self.form.email_to_participant_on_update,
- self.EmailIds.NEW_FORM_REVISION: self.form.email_to_former_participants,
- self.EmailIds.RESEND: self.form.resend_email_to_participant,
- self.EmailIds.INVITE: self.form.email_to_invited_users,
- self.EmailIds.EMAIL_VERIFICATION:
- self.form.verification_email_to_participant,
- }
-
- emailtemplate = emailtemplates[event]
- url = (self.make_new_verification_url()
- if event == self.EmailIds.EMAIL_VERIFICATION
- else self.make_new_auth_url())
- context = {
- 'participant': str(self),
- 'contact': self.form.responsible.contact_display,
- 'form': str(self.form),
- 'url': str(url),
- 'last_modified': localize(self.last_modified, use_l10n=True),
- 'list_unsubscribe': self.list_unsubscribe_link,
- }
- if extra_context:
- context.update(extra_context)
- return EmailMessage.make(emailtemplate, context, self.email)
-
- def resend_auth_link(self) -> 'Optional[EmailMessage]':
- return self.send_participant_email(self.EmailIds.RESEND)
+ EmailMessage.make(participation.form.email_to_responsibles, context, self.email)
+
+ def send_bulk_mail(self) -> 'Optional[EmailMessage]':
+ # FIXME
+ if self.allow_responsible_email:
+ context = {'responsible': str(self),
+ 'form': str(self.form),
+ 'url': self.make_new_auth_url(),
+ 'contact': self.form.responsible.contact_display,
+ 'list_unsubscribe': self.list_unsubscribe_link,
+ }
+ return EmailMessage.make(self.form.bulk_email_to_responsibles, context, self.email)
@property
- def flow(self) -> List[str]:
- from ..urls import participant_flow_urls
-
- rv = [i.name for i in participant_flow_urls]
- if not self.form.questions:
- rv.remove('questions')
- if not self.form.require_email_verification or self.email_verified:
- rv.remove('email_verification')
- if self.form.require_email_verification and not self.email:
- rv.remove('email_verification')
- if not self.form.is_published:
- rv = ['contact_details', 'submitted']
- return rv
-
- def can_access_view(self, view_name: str, auth: bool=False) -> bool:
+ def forms_responsible(self) -> 'List[ServiceForm]':
"""
- Access is granted to next view after last finished view
-
- auth: if query is for authentication (if we can already really proceed to view or not).
+ Return list of serviceform where this member is assigned as responsible to a activity etc.
"""
- if view_name == 'submitted' and not auth:
- return False
- last = self.flow.index(
- self.last_finished_view) if self.last_finished_view in self.flow else -1
- cur = self.flow.index(view_name) if view_name in self.flow else last + 2
- if self.form.flow_by_categories and self.form.allow_skipping_categories:
- # In participation view, allow going straight to questions if skipping categories
- # is allowed
- if self.form.require_email_verification:
- if self.last_finished_view == 'email_verification':
- last += 1
- elif self.last_finished_view == 'contact_details':
- last += 1
-
- return cur <= last + 1
-
- def proceed_to_view(self, next_view: str) -> None:
- if not self.can_access_view(next_view):
- _next = self.flow.index(next_view)
- self.last_finished_view = self.flow[_next - 1]
- self.save(update_fields=['last_finished_view'])
-
- @property
- def next_view_name(self) -> str:
- return self.flow[self.flow.index(self._current_view) + 1]
-
- def redirect_next(self, request: HttpRequest, message: bool=True) -> HttpResponse:
- if self.status == self.STATUS_UPDATING and message:
- messages.warning(request, _(
- 'Updated information has been stored! Please proceed until the end of the form.'))
- return HttpResponseRedirect(reverse(self.next_view_name))
-
- def redirect_last(self) -> HttpResponse:
- last = self.flow.index(
- self.last_finished_view) if self.last_finished_view in self.flow else -1
- return HttpResponseRedirect(reverse(self.flow[last + 1]))
-
- @cached_property
- def log(self) -> 'Sequence[ParticipantLog]':
- return self.participantlog_set.all()
\ No newline at end of file
+ # TODO: implement this.
+ return []
\ No newline at end of file
diff --git a/serviceform/serviceform/models/serviceform.py b/serviceform/serviceform/models/serviceform.py
index 63123da..7b7d087 100644
--- a/serviceform/serviceform/models/serviceform.py
+++ b/serviceform/serviceform/models/serviceform.py
@@ -15,17 +15,19 @@
#
# You should have received a copy of the GNU General Public License
# along with Serviceform. If not, see .
+
import datetime
-import string
import logging
+import string
from enum import Enum
-from typing import Tuple, Set, Optional, Sequence, Iterator, Iterable, TYPE_CHECKING
+from typing import Tuple, Set, Sequence, Iterator, Iterable, TYPE_CHECKING, List, Optional
-from colorful.fields import RGBColorField
from django.conf import settings
from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
from django.db.models import Prefetch
+from django.db.models.signals import post_save
+from django.dispatch import receiver
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils import timezone
@@ -37,13 +39,13 @@
from serviceform.tasks.models import Task
+from ..fields import ColorField
from .. import emails, utils
-from ..utils import ColorStr
+from ..utils import ColorStr, django_cache, invalidate_cache
-from .mixins import SubitemMixin, NameDescriptionMixin, CopyMixin
-from .people import Participant, ResponsibilityPerson
from .email import EmailTemplate
-from .participation import QuestionAnswer
+from .participation import QuestionAnswer, Participation
+from .people import Member, Organization
if TYPE_CHECKING:
from .participation import ParticipationActivity, ParticipationActivityChoice
@@ -52,18 +54,86 @@
logger = logging.getLogger(__name__)
-class ColorField(RGBColorField):
- def get_prep_value(self, value: 'ColorStr') -> 'Optional[ColorStr]':
- rv = super().get_prep_value(value)
- if rv == '#000000':
- rv = None
- return rv
+class AbstractServiceFormItem(models.Model):
+ subitem_name: str = None
+ parent_name: str
+
+ _counter: int
+ _responsibles: Set[Member]
+
+ class Meta:
+ abstract = True
+ ordering = ('order',)
+
+ def __str__(self):
+ return self.name
+
+ name = models.CharField(max_length=256, verbose_name=_('Name'))
+ description = models.TextField(blank=True, verbose_name=_('Description'))
+
+ order = models.PositiveIntegerField(default=0, blank=False, null=False, db_index=True,
+ verbose_name=_('Order'))
+ skip_numbering = models.BooleanField(_('Skip'), default=False)
+ responsibles = select2_fields.ManyToManyField(Member, blank=True,
+ verbose_name=_('Responsible persons'),
+ related_name='%(class)s_responsibles',
+ overlay=_('Choose responsibles'),
+ )
+
+ def __init__(self, *args, **kwargs) -> None:
+ self._responsibles = set()
+ super().__init__(*args, **kwargs)
+
+ # TODO: change this dirty caching to something more clever
+ def has_responsible(self, r: 'Member') -> bool:
+ return r in self._responsibles
+
+ @property
+ def real_responsibles(self) -> List[Member]:
+ """All responsibles of this item + its parents """
+ rs = set(self.responsibles.all())
+ parent = self.parent
+ if parent:
+ rs.update(parent.real_responsibles)
+ return sorted(rs, key=lambda x: (x.surname, x.forenames))
+
+ @property
+ def all_responsibles(self) -> List[Member]:
+ """Collect recursively get all responsibles of this item + all its subitems"""
+ rs = set(self.responsibles.all())
+
+ for subitem in self.sub_items:
+ rs.update(subitem.all_responsibles)
- def from_db_value(self, value: 'Optional[ColorStr]', *args):
- if value is None:
- return '#000000'
- return value
+ return sorted(rs, key=lambda x: (x.surname, x.forenames))
+ @property
+ def parent(self) -> 'Optional[AbstractServiceFormItem]':
+ return getattr(self, self.parent_name, None)
+
+ @cached_property
+ def sub_items(self) -> 'Sequence[AbstractServiceFormItem]':
+ if not self.subitem_name:
+ return []
+
+ return getattr(self, self.subitem_name + '_set').all()
+
+ @cached_property
+ def responsibles_display(self) -> str:
+ first_resp = ''
+ responsibles = self.responsibles.all()
+ if responsibles:
+ first_resp = str(self.responsibles.first())
+ if len(responsibles) > 1:
+ return _('{} (and others)').format(first_resp)
+ else:
+ return first_resp
+
+ def background_color_display(self) -> 'ColorStr':
+ raise NotImplementedError
+
+ def id_display(self):
+ return ''
class FormRevision(models.Model):
@@ -79,9 +149,9 @@ class Meta:
default=datetime.datetime(3000, 1, 1, tzinfo=local_tz))
valid_to = models.DateTimeField(verbose_name=_('Valid to'),
default=datetime.datetime(3000, 1, 1, tzinfo=local_tz))
- send_bulk_email_to_participants = models.BooleanField(
- _('Send bulk email to participants'),
- help_text=_('Send email to participants that filled the form when this revision was '
+ send_bulk_email_to_participations = models.BooleanField(
+ _('Send bulk email to participations'),
+ help_text=_('Send email to participations that filled the form when this revision was '
'active. Email is sent when new current revision is published.'),
default=True)
send_emails_after = models.DateTimeField(
@@ -95,7 +165,7 @@ def __str__(self):
return self.name
-class ServiceForm(SubitemMixin, models.Model):
+class ServiceForm(AbstractServiceFormItem):
subitem_name = 'level1category'
class Meta:
@@ -118,18 +188,21 @@ def __str__(self):
on_delete=models.SET_NULL)
# Ownership
- responsible = models.ForeignKey(ResponsibilityPerson, null=True, blank=True,
+ organization = models.ForeignKey(Organization, on_delete=models.CASCADE)
+
+ #TODO: migrate to .responsibles and remove field
+ responsible = models.ForeignKey(Member, null=True, blank=True,
verbose_name=_('Responsible'), on_delete=models.SET_NULL)
# Email settings
require_email_verification = models.BooleanField(_('Require email verification'), default=True)
- verification_email_to_participant = models.ForeignKey(
+ verification_email_to_participation = models.ForeignKey(
EmailTemplate, null=True, blank=True,
related_name='+',
- verbose_name=_('Verification email to participant'),
+ verbose_name=_('Verification email to participation'),
help_text=_(
- 'Email verification message that is sent to participant when filling form, '
+ 'Email verification message that is sent to participation when filling form, '
'if email verification is enabled'),
on_delete=models.SET_NULL)
@@ -147,6 +220,7 @@ def __str__(self):
related_name='+',
on_delete=models.SET_NULL)
+ # TODO: moved to Organization
email_to_responsible_auth_link = models.ForeignKey(
EmailTemplate, null=True, blank=True,
related_name='+',
@@ -155,34 +229,34 @@ def __str__(self):
help_text=_('Email that is sent to responsible when he requests auth link'),
on_delete=models.SET_NULL)
- # Participant emails:
+ # Participation emails:
# on_finish
- email_to_participant = models.ForeignKey(
+ email_to_participation = models.ForeignKey(
EmailTemplate, null=True, blank=True,
related_name='+',
- verbose_name=_('Email to participant, on finish'),
- help_text=_('Email that is sent to participant after he has fulfilled his participation'),
+ verbose_name=_('Email to participation, on finish'),
+ help_text=_('Email that is sent to participation after he has fulfilled his participation'),
on_delete=models.SET_NULL)
# on update
- email_to_participant_on_update = models.ForeignKey(EmailTemplate, null=True, blank=True,
+ email_to_participation_on_update = models.ForeignKey(EmailTemplate, null=True, blank=True,
related_name='+', verbose_name=_(
- 'Email to participant, on update'), help_text=_(
- 'Email that is sent to participant after he has updated his participation'),
+ 'Email to participation, on update'), help_text=_(
+ 'Email that is sent to participation after he has updated his participation'),
on_delete=models.SET_NULL)
# resend
- resend_email_to_participant = models.ForeignKey(
+ resend_email_to_participation = models.ForeignKey(
EmailTemplate, null=True, blank=True,
related_name='+',
- verbose_name=_('Resend email to participant'),
- help_text=_('Email that is sent to participant if he requests resending email'),
+ verbose_name=_('Resend email to participation'),
+ help_text=_('Email that is sent to participation if he requests resending email'),
on_delete=models.SET_NULL)
# new_form_revision
- email_to_former_participants = models.ForeignKey(
+ email_to_former_participations = models.ForeignKey(
EmailTemplate, null=True, blank=True,
related_name='+',
- verbose_name=_('Bulk email to former participants'),
- help_text=_('Email that is sent to former participants when form is published'),
+ verbose_name=_('Bulk email to former participations'),
+ help_text=_('Email that is sent to former participations when form is published'),
on_delete=models.SET_NULL)
# invite
email_to_invited_users = models.ForeignKey(
@@ -204,7 +278,7 @@ def __str__(self):
password = models.CharField(
_('Password'), max_length=32, blank=True,
- help_text=_('Password that is asked from participants'),
+ help_text=_('Password that is asked from participations'),
default='')
hide_contact_details = models.BooleanField(
@@ -259,6 +333,11 @@ def can_access(self) -> str:
can_access.short_description = _('Can access')
+ @property
+ @django_cache('all_responsibles')
+ def all_responsibles(self):
+ return super().all_responsibles
+
@cached_property
def sub_items(self) -> 'Sequence[AbstractServiceFormItem]':
lvl2s = Prefetch('level2category_set',
@@ -275,11 +354,12 @@ def create_initial_data(self) -> None:
self.create_email_templates()
self.current_revision = FormRevision.objects.create(name='%s' % timezone.now().year,
form=self)
- self.responsible = ResponsibilityPerson.objects.create(
+ self.responsible = Member.objects.create(
forenames=_('Default'),
surname=_('Responsible'),
email=_('defaultresponsible@email.com'),
- form=self)
+ organization_id=self.organization_id
+ )
self.save()
def create_email_templates(self) -> None:
@@ -292,61 +372,61 @@ def create_email_templates(self) -> None:
if not self.bulk_email_to_responsibles:
commit = True
self.bulk_email_to_responsibles = EmailTemplate.make(
- _('Default bulk email to responsibles'), self,
+ _('Default bulk email to responsibles'), self.organization,
emails.bulk_email_to_responsibles,
_('Participations can be now viewed for form {{form}}'))
if not self.email_to_responsibles:
commit = True
self.email_to_responsibles = EmailTemplate.make(
_('Default email to responsibles'),
- self, emails.message_to_responsibles,
+ self.organization, emails.message_to_responsibles,
_('New participation arrived for form {{form}}'))
- if not self.email_to_participant:
+ if not self.email_to_participation:
commit = True
- self.email_to_participant = EmailTemplate.make(
- _('Default email to participant, on finish'), self,
- emails.participant_on_finish,
+ self.email_to_participation = EmailTemplate.make(
+ _('Default email to participation, on finish'), self.organization,
+ emails.participation_on_finish,
_('Your update to form {{form}}'))
- if not self.email_to_participant_on_update:
+ if not self.email_to_participation_on_update:
commit = True
- self.email_to_participant_on_update = EmailTemplate.make(
- _('Default email to participant, on update'), self,
- emails.participant_on_update,
+ self.email_to_participation_on_update = EmailTemplate.make(
+ _('Default email to participation, on update'), self.organization,
+ emails.participation_on_update,
_('Your updated participation to form {{form}}'))
- if not self.email_to_former_participants:
+ if not self.email_to_former_participations:
commit = True
- self.email_to_former_participants = EmailTemplate.make(
- _('Default email to former participants'), self,
- emails.participant_new_form_revision,
+ self.email_to_former_participations = EmailTemplate.make(
+ _('Default email to former participations'), self.organization,
+ emails.participation_new_form_revision,
_('New form revision to form {{form}} has been published'))
- if not self.resend_email_to_participant:
+ if not self.resend_email_to_participation:
commit = True
- self.resend_email_to_participant = EmailTemplate.make(
- _('Default resend email to participant'), self,
- emails.resend_email_to_participants,
+ self.resend_email_to_participation = EmailTemplate.make(
+ _('Default resend email to participation'), self.organization,
+ emails.resend_email_to_participations,
_('Your participation to form {{form}}'))
if not self.email_to_invited_users:
commit = True
self.email_to_invited_users = EmailTemplate.make(
- _('Default invite email to participants'), self,
+ _('Default invite email to participations'), self.organization,
emails.invite,
_('Invitation to fill participation in {{form}}'))
if not self.email_to_responsible_auth_link:
commit = True
self.email_to_responsible_auth_link = EmailTemplate.make(
- _('Default request responsible auth link email'), self,
+ _('Default request responsible auth link email'), self.organization,
emails.request_responsible_auth_link,
_('Your report in {{form}}'))
- if not self.verification_email_to_participant:
+ if not self.verification_email_to_participation:
commit = True
- self.verification_email_to_participant = EmailTemplate.make(
- _('Default verification email to participant'), self,
- emails.verification_email_to_participant,
+ self.verification_email_to_participation = EmailTemplate.make(
+ _('Default verification email to participation'), self.organization,
+ emails.verification_email_to_participation,
_('Please verify your email in {{form}}'))
if commit:
self.save()
- def invite_user(self, email: str, old_participants: bool=False) -> InviteUserResponse:
+ def invite_user(self, email: str, old_participations: bool=False) -> InviteUserResponse:
"""
Create new participations to current form version and send invites
@@ -354,19 +434,22 @@ def invite_user(self, email: str, old_participants: bool=False) -> InviteUserRes
"""
logger.info('Invite user %s %s', self, email)
- participant = Participant.objects.filter(email=email, form_revision__form=self).first()
- if participant:
- if old_participants and participant.form_revision != self.current_revision:
- rv = participant.send_participant_email(Participant.EmailIds.INVITE)
+ participation = Participation.objects.filter(
+ member__email=email, form_revision__form=self).first()
+ if participation:
+ if old_participations and participation.form_revision != self.current_revision:
+ rv = participation.send_participation_email(Participation.EmailIds.INVITE)
return (self.InviteUserResponse.EMAIL_SENT
if rv else self.InviteUserResponse.USER_DENIED_EMAIL)
else:
return self.InviteUserResponse.USER_EXISTS
else:
- participant = Participant.objects.create(email=email,
- form_revision=self.current_revision,
- status=Participant.STATUS_INVITED)
- participant.send_participant_email(Participant.EmailIds.INVITE)
+ member, created = Member.objects.get_or_create(organization=self.organization,
+ email=email)
+ participation = Participation.objects.create(member=member,
+ form_revision=self.current_revision,
+ status=Participation.STATUS_INVITED)
+ participation.send_participation_email(Participation.EmailIds.INVITE)
return self.InviteUserResponse.EMAIL_SENT
@cached_property
@@ -425,10 +508,10 @@ def links(self) -> Tuple[str]:
def participation_count(self) -> str:
if self.current_revision:
old_time = timezone.now() - datetime.timedelta(minutes=20)
- ready = self.current_revision.participant_set.filter(
- status__in=Participant.READY_STATUSES)
- recent_ongoing = self.current_revision.participant_set.filter(
- status__in=[Participant.STATUS_ONGOING],
+ ready = self.current_revision.participation_set.filter(
+ status__in=Participation.READY_STATUSES)
+ recent_ongoing = self.current_revision.participation_set.filter(
+ status__in=[Participation.STATUS_ONGOING],
last_modified__gt=old_time)
return '%s + %s' % (ready.count(), recent_ongoing.count())
@@ -440,18 +523,18 @@ def participation_count(self) -> str:
def bulk_email_responsibles(self) -> None:
logger.info('Bulk email responsibles %s', self)
- for r in self.responsibilityperson_set.all():
+ for r in self.all_responsibles:
r.send_bulk_mail()
- def bulk_email_former_participants(self) -> None:
- logger.info('Bulk email former participants %s', self)
- for p in Participant.objects.filter(send_email_allowed=True,
- form_revision__send_bulk_email_to_participants=True,
- form_revision__form=self,
- form_revision__valid_to__lt=timezone.now(),
- status__in=Participant.READY_STATUSES
- ).distinct():
- p.send_participant_email(Participant.EmailIds.NEW_FORM_REVISION)
+ def bulk_email_former_participations(self) -> None:
+ logger.info('Bulk email former participations %s', self)
+ for p in Participation.objects.filter(member__allow_participation_email=True,
+ form_revision__send_bulk_email_to_participations=True,
+ form_revision__form=self,
+ form_revision__valid_to__lt=timezone.now(),
+ status__in=Participant.READY_STATUSES
+ ).distinct():
+ p.send_participation_email(Participation.EmailIds.NEW_FORM_REVISION)
def reschedule_bulk_email(self) -> None:
now = timezone.now()
@@ -466,42 +549,17 @@ def reschedule_bulk_email(self) -> None:
tr = Task.make(self.bulk_email_responsibles,
scheduled_time=self.current_revision.send_emails_after)
if self.current_revision.valid_from > now:
- tp = Task.make(self.bulk_email_former_participants,
+ tp = Task.make(self.bulk_email_former_participations,
scheduled_time=self.current_revision.valid_from)
-class AbstractServiceFormItem(models.Model):
- _responsibles: Set[ResponsibilityPerson]
- sub_items: 'Iterable[AbstractServiceFormItem]'
-
- class Meta:
- abstract = True
- ordering = ('order',)
+@receiver(post_save, sender=ServiceForm)
+def invalidate_serviceform_caches(sender: ServiceForm, **kwargs):
+ invalidate_cache(sender, 'all_responsibles')
- order = models.PositiveIntegerField(default=0, blank=False, null=False, db_index=True,
- verbose_name=_('Order'))
- responsibles = select2_fields.ManyToManyField(ResponsibilityPerson, blank=True,
- verbose_name=_('Responsible persons'),
- related_name='%(class)s_related',
- overlay=_('Choose responsibles'),
- )
- @cached_property
- def responsibles_display(self) -> str:
- first_resp = ''
- responsibles = self.responsibles.all()
- if responsibles:
- first_resp = str(self.responsibles.first())
- if len(responsibles) > 1:
- return _('{} (and others)').format(first_resp)
- else:
- return first_resp
-
- def background_color_display(self) -> 'ColorStr':
- raise NotImplementedError
-
-
-class Level1Category(SubitemMixin, NameDescriptionMixin, AbstractServiceFormItem):
+class Level1Category(AbstractServiceFormItem):
+ parent_name = 'form'
subitem_name = 'level2category'
background_color = ColorField(_('Background color'), blank=True, null=True)
@@ -516,7 +574,8 @@ def background_color_display(self) -> 'ColorStr':
return utils.not_black(self.background_color) or utils.not_black(self.form.level1_color)
-class Level2Category(SubitemMixin, NameDescriptionMixin, AbstractServiceFormItem):
+class Level2Category(AbstractServiceFormItem):
+ parent_name = 'category'
subitem_name = 'activity'
background_color = ColorField(_('Background color'), blank=True, null=True)
@@ -535,8 +594,9 @@ def background_color_display(self) -> 'ColorStr':
utils.not_black(self.category.form.level2_color))
-class Activity(SubitemMixin, NameDescriptionMixin, AbstractServiceFormItem):
+class Activity(AbstractServiceFormItem):
subitem_name = 'activitychoice'
+ parent_name = 'category'
class Meta(AbstractServiceFormItem.Meta):
verbose_name = _('Activity')
@@ -546,7 +606,6 @@ class Meta(AbstractServiceFormItem.Meta):
on_delete=models.CASCADE)
multiple_choices_allowed = models.BooleanField(default=True, verbose_name=_('Multichoice'))
people_needed = models.PositiveIntegerField(_('Needed'), default=0)
- skip_numbering = models.BooleanField(_('Skip'), default=False)
@property
def has_choices(self) -> bool:
@@ -560,14 +619,14 @@ def participation_items(self, revision_name: str) -> 'Sequence[ParticipationActi
current_revision = self.category.category.form.current_revision
qs = self.participationactivity_set.filter(
- participant__status__in=Participant.READY_STATUSES)
+ participation__status__in=Participation.READY_STATUSES)
if revision_name == utils.RevisionOptions.ALL:
- qs = qs.order_by('participant__form_revision')
+ qs = qs.order_by('participation__form_revision')
elif revision_name == utils.RevisionOptions.CURRENT:
- qs = qs.filter(participant__form_revision=current_revision)
+ qs = qs.filter(participation__form_revision=current_revision)
else:
- qs = qs.filter(participant__form_revision__name=revision_name)
+ qs = qs.filter(participation__form_revision__name=revision_name)
return qs
@property
@@ -581,14 +640,14 @@ def background_color_display(self) -> 'ColorStr':
self.category.background_color_display)
-class ActivityChoice(SubitemMixin, NameDescriptionMixin, AbstractServiceFormItem):
+class ActivityChoice(AbstractServiceFormItem):
+ parent_name = 'activity'
class Meta(AbstractServiceFormItem.Meta):
verbose_name = _('Activity choice')
verbose_name_plural = _('Activity choices')
activity = models.ForeignKey(Activity, on_delete=models.CASCADE)
people_needed = models.PositiveIntegerField(_('Needed'), default=0)
- skip_numbering = models.BooleanField(_('Skip'), default=False)
@property
def id_display(self) -> str:
@@ -603,14 +662,14 @@ def participation_items(self, revision_name: str) -> 'Sequence[ParticipationActi
current_revision = self.activity.category.category.form.current_revision
qs = self.participationactivitychoice_set.filter(
- activity__participant__status__in=Participant.READY_STATUSES)
+ activity__participation__status__in=Participation.READY_STATUSES)
if revision_name == utils.RevisionOptions.ALL:
- qs = qs.order_by('activity__participant__form_revision')
+ qs = qs.order_by('activity__participation__form_revision')
elif revision_name == utils.RevisionOptions.CURRENT:
- qs = qs.filter(activity__participant__form_revision=current_revision)
+ qs = qs.filter(activity__participation__form_revision=current_revision)
else:
- qs = qs.filter(activity__participant__form_revision__name=revision_name)
+ qs = qs.filter(activity__participation__form_revision__name=revision_name)
return qs
@cached_property
@@ -619,7 +678,7 @@ def background_color_display(self) -> 'ColorStr':
self.activity.category.background_color_display)
-class Question(CopyMixin, AbstractServiceFormItem):
+class Question(AbstractServiceFormItem):
class Meta(AbstractServiceFormItem.Meta):
verbose_name = _('Question')
verbose_name_plural = _('Questions')
@@ -650,18 +709,23 @@ def render(self) -> str:
def questionanswers(self, revision_name: str) -> 'Sequence[QuestionAnswer]':
qs = QuestionAnswer.objects.filter(question=self,
- participant__status__in=Participant.READY_STATUSES)
+ participation__status__in=Participation.READY_STATUSES)
current_revision = self.form.current_revision
if revision_name == utils.RevisionOptions.ALL:
- qs = qs.order_by('-participant__form_revision')
+ qs = qs.order_by('-participation__form_revision')
elif revision_name == utils.RevisionOptions.CURRENT:
- qs = qs.filter(participant__form_revision=current_revision)
+ qs = qs.filter(participation__form_revision=current_revision)
else:
- qs = qs.filter(participant__form_revision__name=revision_name)
+ qs = qs.filter(participation__form_revision__name=revision_name)
return qs
+ @property
+ def id_display(self):
+ # TODO: perhaps we could implement also here some kind of numbering
+ return ''
+
def __str__(self):
return self.question
diff --git a/serviceform/serviceform/static/serviceform/serviceform.scss b/serviceform/serviceform/static/serviceform/serviceform.scss
index f981131..4d63837 100644
--- a/serviceform/serviceform/static/serviceform/serviceform.scss
+++ b/serviceform/serviceform/static/serviceform/serviceform.scss
@@ -413,15 +413,15 @@ ul.participation-flow {
margin-left: 20px;
}
-$indent-participant: 25pt;
+$indent-participation: 25pt;
.row-report {
@extend .row;
}
-.row-report-participant {
+.row-report-participation {
@extend .row-report;
- margin-left: $indent-participant;
+ margin-left: $indent-participation;
}
@@ -432,7 +432,7 @@ $indent-participant: 25pt;
.row-report-extra {
@extend .row, .panel, .panel-body;
- //margin-left: $indent-participant;
+ //margin-left: $indent-participation;
background: rgba(203, 0, 255, 0.06);
}
/*
@@ -456,7 +456,7 @@ $indent-participant: 25pt;
@extend .col-md-1;
}
-.col-report-participant {
+.col-report-participation {
@extend .col-xs-6;
@extend .col-md-3;
}
@@ -524,7 +524,7 @@ $indent-participant: 25pt;
@extend .col-xs-1;
}
-.col-report-old-participant {
+.col-report-old-participation {
@extend .col-xs-2;
}
diff --git a/serviceform/serviceform/tasks.py b/serviceform/serviceform/tasks.py
index e2478e0..741cb6e 100644
--- a/serviceform/serviceform/tasks.py
+++ b/serviceform/serviceform/tasks.py
@@ -32,17 +32,17 @@
@shared_task
def cleanup_abandoned_participations():
logger.info('Deleting abandoned participations')
- models.Participant.objects.filter(last_modified__lt=timezone.now() - timedelta(days=1),
- status=models.Participant.STATUS_ONGOING).delete()
+ models.Participation.objects.filter(last_modified__lt=timezone.now() - timedelta(days=1),
+ status=models.Participation.STATUS_ONGOING).delete()
@shared_task
def finish_abandoned_updating_participations():
- for p in models.Participant.objects.filter(
+ for p in models.Participation.objects.filter(
last_modified__lt=timezone.now() - timedelta(days=1),
- status=models.Participant.STATUS_UPDATING):
+ status=models.Participation.STATUS_UPDATING):
logger.info('Finishing abandoned updating participations %s', p)
- p.finish(email_participant=False)
+ p.finish(email_participation=False)
def test_task():
diff --git a/serviceform/serviceform/templates/serviceform/login/send_responsible_auth_link.html b/serviceform/serviceform/templates/serviceform/login/send_member_auth_link.html
similarity index 62%
rename from serviceform/serviceform/templates/serviceform/login/send_responsible_auth_link.html
rename to serviceform/serviceform/templates/serviceform/login/send_member_auth_link.html
index 2a5256c..8e1ea5c 100644
--- a/serviceform/serviceform/templates/serviceform/login/send_responsible_auth_link.html
+++ b/serviceform/serviceform/templates/serviceform/login/send_member_auth_link.html
@@ -5,5 +5,10 @@
{% blocktrans %}If you are marked as a responsible person, please give your email below so
we can send you a personal link to view your participation report.{% endblocktrans%}
+
+ {% blocktrans %}If you have filled this form already in the past, please give your email
+ below so we can send you a personal link to update your participation information.
+ {% endblocktrans %}
+
{% crispy email_form %}
{% endblock %}
\ No newline at end of file
diff --git a/serviceform/serviceform/templates/serviceform/login/send_participant_auth_link.html b/serviceform/serviceform/templates/serviceform/login/send_participation_auth_link.html
similarity index 100%
rename from serviceform/serviceform/templates/serviceform/login/send_participant_auth_link.html
rename to serviceform/serviceform/templates/serviceform/login/send_participation_auth_link.html
diff --git a/serviceform/serviceform/templates/serviceform/login/unsubscribe_member.html b/serviceform/serviceform/templates/serviceform/login/unsubscribe_member.html
new file mode 100644
index 0000000..d63ae2c
--- /dev/null
+++ b/serviceform/serviceform/templates/serviceform/login/unsubscribe_member.html
@@ -0,0 +1,6 @@
+{#% extends "serviceform/participation/login_base.html" TODO %#}
+{# TODO add link to adjust settings #}
+{% load i18n %}
+{% block content %}
+{% trans "You won't be getting any emails "%}{{service_form}}{% trans " from this system any more."%}
+{% endblock %}
\ No newline at end of file
diff --git a/serviceform/serviceform/templates/serviceform/login/unsubscribe_participant.html b/serviceform/serviceform/templates/serviceform/login/unsubscribe_participant.html
deleted file mode 100644
index 97a88cc..0000000
--- a/serviceform/serviceform/templates/serviceform/login/unsubscribe_participant.html
+++ /dev/null
@@ -1,5 +0,0 @@
-{% extends "serviceform/participation/login_base.html" %}
-{% load i18n %}
-{% block content %}
-{% trans "You won't be getting any emails (as participant for "%}{{service_form}}{% trans ") from this system any more."%}
-{% endblock %}
\ No newline at end of file
diff --git a/serviceform/serviceform/templates/serviceform/login/unsubscribe_responsible.html b/serviceform/serviceform/templates/serviceform/login/unsubscribe_responsible.html
deleted file mode 100644
index 9e30b33..0000000
--- a/serviceform/serviceform/templates/serviceform/login/unsubscribe_responsible.html
+++ /dev/null
@@ -1,5 +0,0 @@
-{% extends "serviceform/participation/login_base.html" %}
-{% load i18n %}
-{% block content %}
-{% trans "You won't be getting any emails (as responsible for "%}{{service_form}}{% trans ") from this system any more."%}
-{% endblock %}
\ No newline at end of file
diff --git a/serviceform/serviceform/templates/serviceform/member/main.html b/serviceform/serviceform/templates/serviceform/member/main.html
new file mode 100644
index 0000000..4580916
--- /dev/null
+++ b/serviceform/serviceform/templates/serviceform/member/main.html
@@ -0,0 +1,20 @@
+Member page
+
+Participations:
+
+{% for p in member.participation_set.all %}
+-
+ {{ p }}
+
+{% endfor %}
+
+
+Responsibilities:
+
+{% for f in member.forms_responsible %}
+-
+ {{f}}
+
+{% endfor %}
+
+
diff --git a/serviceform/serviceform/templates/serviceform/participation/contact_view.html b/serviceform/serviceform/templates/serviceform/participation/contact_view.html
index 89d381c..328ea82 100644
--- a/serviceform/serviceform/templates/serviceform/participation/contact_view.html
+++ b/serviceform/serviceform/templates/serviceform/participation/contact_view.html
@@ -3,8 +3,8 @@
{%block content %}
{{ block.super}}
-{% if participant.is_updating %}
-
{% trans "You can delete your participation from the system by clicking"%} {% trans "here"%}.
+{% if participation %}
+ {% trans "You can delete your participation from the system by clicking"%} {% trans "here"%}.
{% trans "Otherwise, please check if the information is correct and update it if necessary. " %}
{% else %}
{% trans "Please fill in your contact details"%}
diff --git a/serviceform/serviceform/templates/serviceform/participation/email_verification.html b/serviceform/serviceform/templates/serviceform/participation/email_verification.html
index 83c5d36..b12a350 100644
--- a/serviceform/serviceform/templates/serviceform/participation/email_verification.html
+++ b/serviceform/serviceform/templates/serviceform/participation/email_verification.html
@@ -3,6 +3,6 @@
{%block content %}
{{ block.super}}
-{% trans "Email sent to"%} {{participant.email}}.
+
{% trans "Email sent to"%} {{participation.member.email}}.
{%trans "Please verify your email address by clicking the link that is given in email. Then you can continue filling the form."%}
{% endblock%}
\ No newline at end of file
diff --git a/serviceform/serviceform/templates/serviceform/participation/participation_flow_base.html b/serviceform/serviceform/templates/serviceform/participation/participation_flow_base.html
index 01acb93..26c836b 100644
--- a/serviceform/serviceform/templates/serviceform/participation/participation_flow_base.html
+++ b/serviceform/serviceform/templates/serviceform/participation/participation_flow_base.html
@@ -12,7 +12,7 @@
{% if not readonly %}
- {% participant_flow_menu_items as m_items %}
+ {% participation_flow_menu_items as m_items %}
{% for m in m_items %}
-
- {% participant_flow_categories as m_items %}
+ {% participation_flow_categories as m_items %}
{% for m in m_items %}
-
{% blocktrans %}Contact details{% endblocktrans %}:
- {% for detail, data in participant.contact_details %}
+ {% for detail, data in participation.member.contact_details %}
{{ detail }}
@@ -21,7 +21,7 @@
{% blocktrans %}Contact details{% endblocktrans %}:
@@ -37,7 +37,7 @@
{% blocktrans %}I am wishing to participate in the following activities{% en
- {% for a in participant.activities %}
+ {% for a in participation.activities %}
{{ a.activity.name }}
@@ -60,7 +60,7 @@
{% blocktrans %}I am wishing to participate in the following activities{% en
@@ -76,7 +76,7 @@
{% blocktrans %}Answers to questions{% endblocktrans %}:
{% trans "Answer" %}
- {% for q in participant.questions %}
+ {% for q in participation.questions %}
{{ q.question.question }}
@@ -97,7 +97,7 @@
{% blocktrans %}Answers to questions{% endblocktrans %}:
diff --git a/serviceform/serviceform/templates/serviceform/participation/question_form/question_form.html b/serviceform/serviceform/templates/serviceform/participation/question_form/question_form.html
index 464a7d3..2aa9cd2 100644
--- a/serviceform/serviceform/templates/serviceform/participation/question_form/question_form.html
+++ b/serviceform/serviceform/templates/serviceform/participation/question_form/question_form.html
@@ -7,7 +7,7 @@
{% endif %}
-{% for question in participant.form.questions %}
+{% for question in participation.form.questions %}
{% if question.error %}
diff --git a/serviceform/serviceform/templates/serviceform/reports/all_activities.html b/serviceform/serviceform/templates/serviceform/reports/all_activities.html
index 60f5802..d85277c 100644
--- a/serviceform/serviceform/templates/serviceform/reports/all_activities.html
+++ b/serviceform/serviceform/templates/serviceform/reports/all_activities.html
@@ -2,7 +2,7 @@
{% load i18n serviceform_tags %}
{% block content %}
{% include "serviceform/reports/snippets/_help.html" %}
- {% participants as ps %}
+ {% participations as ps %}
{% include "serviceform/reports/contents/_all_activities.html"%}
{% endblock %}
diff --git a/serviceform/serviceform/templates/serviceform/reports/all_participants.html b/serviceform/serviceform/templates/serviceform/reports/all_participations.html
similarity index 64%
rename from serviceform/serviceform/templates/serviceform/reports/all_participants.html
rename to serviceform/serviceform/templates/serviceform/reports/all_participations.html
index 4c5622b..22dd74d 100644
--- a/serviceform/serviceform/templates/serviceform/reports/all_participants.html
+++ b/serviceform/serviceform/templates/serviceform/reports/all_participations.html
@@ -2,6 +2,6 @@
{% load i18n serviceform_tags %}
{% block content %}
{% include "serviceform/reports/snippets/_help.html" %}
- {% participants as ps %}
- {% include "serviceform/reports/contents/_all_participants.html"%}
+ {% participations as ps %}
+ {% include "serviceform/reports/contents/_all_participations.html"%}
{% endblock %}
diff --git a/serviceform/serviceform/templates/serviceform/reports/all_questions.html b/serviceform/serviceform/templates/serviceform/reports/all_questions.html
index 68f5693..e5515eb 100644
--- a/serviceform/serviceform/templates/serviceform/reports/all_questions.html
+++ b/serviceform/serviceform/templates/serviceform/reports/all_questions.html
@@ -1,6 +1,6 @@
{% extends "serviceform/reports/base/report_base.html" %}
{% load i18n serviceform_tags %}
{% block content %}
- {% participants as ps %}
+ {% participations as ps %}
{% include "serviceform/reports/contents/_all_questions.html"%}
{% endblock %}
diff --git a/serviceform/serviceform/templates/serviceform/reports/contents/_all_activities.html b/serviceform/serviceform/templates/serviceform/reports/contents/_all_activities.html
index c3b8caa..c6fe6b7 100644
--- a/serviceform/serviceform/templates/serviceform/reports/contents/_all_activities.html
+++ b/serviceform/serviceform/templates/serviceform/reports/contents/_all_activities.html
@@ -23,12 +23,12 @@
{% trans "All activities" %}
({{ p_items|length }}{%if c.people_needed%}/{{ c.people_needed }}{%endif%})
{% for pc in p_items %}
- {% include "serviceform/reports/snippets/_participant_row.html" with item=pc participant=pc.cached_participant %}
+ {% include "serviceform/reports/snippets/_participation_row.html" with item=pc participation=pc.cached_participation %}
{% endfor %}
{% endfor %}
{% else %}
{% for pa in p_items %}
- {% include "serviceform/reports/snippets/_participant_row.html" with item=pa participant=pa.cached_participant %}
+ {% include "serviceform/reports/snippets/_participation_row.html" with item=pa participation=pa.cached_participation %}
{% endfor %}
{% endif %}
{% endfor %}
diff --git a/serviceform/serviceform/templates/serviceform/reports/contents/_all_participants.html b/serviceform/serviceform/templates/serviceform/reports/contents/_all_participants.html
deleted file mode 100644
index ad14495..0000000
--- a/serviceform/serviceform/templates/serviceform/reports/contents/_all_participants.html
+++ /dev/null
@@ -1,7 +0,0 @@
-{% load i18n %}
-
{% trans "All participants" %} ({{ps|length}})
- {% for p in ps %}
- {% include "serviceform/reports/snippets/_participant_row.html" with participant=p %}
- {% endfor %}
-
-
diff --git a/serviceform/serviceform/templates/serviceform/reports/contents/_all_participations.html b/serviceform/serviceform/templates/serviceform/reports/contents/_all_participations.html
new file mode 100644
index 0000000..cb38081
--- /dev/null
+++ b/serviceform/serviceform/templates/serviceform/reports/contents/_all_participations.html
@@ -0,0 +1,7 @@
+{% load i18n %}
+
{% trans "All participations" %} ({{ps|length}})
+ {% for p in ps %}
+ {% include "serviceform/reports/snippets/_participation_row.html" with participation=p %}
+ {% endfor %}
+
+
diff --git a/serviceform/serviceform/templates/serviceform/reports/contents/_all_questions.html b/serviceform/serviceform/templates/serviceform/reports/contents/_all_questions.html
index 93bef75..45d0b9f 100644
--- a/serviceform/serviceform/templates/serviceform/reports/contents/_all_questions.html
+++ b/serviceform/serviceform/templates/serviceform/reports/contents/_all_questions.html
@@ -8,9 +8,9 @@
{% trans "Answers to questions" %}
{% if pq.answer %}
{% all_revisions as ar%}
{% if ar %}
- {{pq.participant.form_revision}}
+ {{pq.participation.form_revision}}
{% endif %}
- {{ pq.cached_participant }}:
+ {{ pq.cached_participation.member }}:
{% if pq.question.answer_type == 'boolean' %}
{% if pq.answer %}
{% trans "Yes" %}
diff --git a/serviceform/serviceform/templates/serviceform/reports/contents/_all_responsibles.html b/serviceform/serviceform/templates/serviceform/reports/contents/_all_responsibles.html
index f735042..9850f41 100644
--- a/serviceform/serviceform/templates/serviceform/reports/contents/_all_responsibles.html
+++ b/serviceform/serviceform/templates/serviceform/reports/contents/_all_responsibles.html
@@ -1,5 +1,5 @@
{% load i18n %}
{% trans "Responsible contact persons" %} ({{service_form.responsibilityperson_set.all|length}})
- {% for p in service_form.responsibilityperson_set.all %}
- {% include "serviceform/reports/snippets/_participant_row.html" with participant=p is_responsible=1 %}
+ {% for p in service_form.all_responsibles %}
+ {% include "serviceform/reports/snippets/_participation_row.html" with participation=p is_responsible=1 %}
{% endfor %}
\ No newline at end of file
diff --git a/serviceform/serviceform/templates/serviceform/reports/contents/_responsible_contents.html b/serviceform/serviceform/templates/serviceform/reports/contents/_responsible_contents.html
index 4c401a2..3a6b35d 100644
--- a/serviceform/serviceform/templates/serviceform/reports/contents/_responsible_contents.html
+++ b/serviceform/serviceform/templates/serviceform/reports/contents/_responsible_contents.html
@@ -22,14 +22,14 @@ {% trans "Participation to activities" %}
{% participation_items c as p_items %}
{% for pc in p_items %}
- {% include "serviceform/reports/snippets/_participant_row.html" with item=pc participant=pc.cached_participant %}
+ {% include "serviceform/reports/snippets/_participation_row.html" with item=pc participation=pc.cached_participation %}
{% endfor %}
{% endif %}
{% endfor %}
{% else %}
{% participation_items a as p_items %}
{% for pa in p_items %}
- {% include "serviceform/reports/snippets/_participant_row.html" with item=pa participant=pa.cached_participant %}
+ {% include "serviceform/reports/snippets/_participation_row.html" with item=pa participation=pa.cached_participation %}
{% endfor %}
{% endif %}
{% endif %}
@@ -41,12 +41,13 @@ {% trans "Participation to activities" %}
{% trans "Answers to questions" %}
{% for q in service_form.questions %}
- {% if q.responsible == responsible %}
+ {% has_responsible q responsible as q_hr %}
+ {% if q_hr %}
{{ q.id_display }} {{ q.question }}
{% for pq in q.questionanswers %}
{% if pq.answer %}
- - {{ pq.participant }}:
+
- {{ pq.participation }}:
{% if pq.question.answer_type == 'boolean' %}
{% if pq.answer %}
{% trans "Yes" %}
diff --git a/serviceform/serviceform/templates/serviceform/reports/edit_responsible.html b/serviceform/serviceform/templates/serviceform/reports/edit_responsible.html
index 31897bf..8cd74ed 100644
--- a/serviceform/serviceform/templates/serviceform/reports/edit_responsible.html
+++ b/serviceform/serviceform/templates/serviceform/reports/edit_responsible.html
@@ -1,4 +1,4 @@
-{% extends "serviceform/reports/base/anonymous_report_base.html" %}
+{# extends "serviceform/reports/base/anonymous_report_base.html" TODO #}
{% load i18n crispy_forms_tags %}
{%block content %}
{% trans "Edit details"%}
diff --git a/serviceform/serviceform/templates/serviceform/reports/report.html b/serviceform/serviceform/templates/serviceform/reports/report.html
index 28d8dd3..30b32bb 100644
--- a/serviceform/serviceform/templates/serviceform/reports/report.html
+++ b/serviceform/serviceform/templates/serviceform/reports/report.html
@@ -3,9 +3,9 @@
{% block content %}
{{ block.super }}
{% include "serviceform/reports/contents/_all_responsibles.html" %}
- {% participants as ps %}
+ {% participations as ps %}
- {% include "serviceform/reports/contents/_all_participants.html"%}
+ {% include "serviceform/reports/contents/_all_participations.html"%}
{% include "serviceform/reports/contents/_all_activities.html"%}
{% include "serviceform/reports/contents/_all_questions.html"%}
diff --git a/serviceform/serviceform/templates/serviceform/reports/snippets/_help.html b/serviceform/serviceform/templates/serviceform/reports/snippets/_help.html
index b6ec4fc..9725516 100644
--- a/serviceform/serviceform/templates/serviceform/reports/snippets/_help.html
+++ b/serviceform/serviceform/templates/serviceform/reports/snippets/_help.html
@@ -1,6 +1,6 @@
{% load i18n %}
-
{% trans "Each participant rows contains the following data"%}
+
{% trans "Each participation rows contains the following data"%}