diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2020-03-06 11:41:54 +0100 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2020-03-06 11:44:44 +0100 |
commit | 3808df22596ff03e8b24c9b97506f68c86197c60 (patch) | |
tree | 87dc15b6b07b78eb8aed52a707c9adab5ae89a9f | |
parent | 2ebb25f9a93d9b840162ca035c243354a3169a87 (diff) | |
download | Ishtar-3808df22596ff03e8b24c9b97506f68c86197c60.tar.bz2 Ishtar-3808df22596ff03e8b24c9b97506f68c86197c60.zip |
Sheet actions: add duplicate for site, operation, document and context record
21 files changed, 784 insertions, 192 deletions
diff --git a/CHANGES.md b/CHANGES.md index 4189a0c15..82aaa3d2a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,11 @@ Ishtar changelog ================ +v3.0.4 - 2020-XX-XX +------------------- +### Features ### +- Sheet actions: add duplicate for site, operation, document and context record + v3.0.3 - 2020-02-24 ------------------- ### Bug fixes ### @@ -21,5 +26,4 @@ v3.0.1 - 2020-02-11 v3.0.0 ------ - A lot of changes since v2. Main change is python3 and Django 1.11 support. diff --git a/archaeological_context_records/forms.py b/archaeological_context_records/forms.py index 2061ecc1f..87cad2c66 100644 --- a/archaeological_context_records/forms.py +++ b/archaeological_context_records/forms.py @@ -147,6 +147,27 @@ class RecordFormMultiSelection(LockForm, MultiSearchForm): validators=[valid_ids(models.ContextRecord)]) +def get_init_parcel(form, operation, prefix=""): + parcels = operation.parcels.all() + sort = lambda x: (x.town.name, x.section) + parcels = sorted(parcels, key=sort) + for key, gparcels in groupby(parcels, sort): + form.fields[prefix + 'parcel'].choices.append( + (" - ".join([k for k in key if k]), + [(parcel.pk, parcel.short_label) for parcel in gparcels]) + ) + if len(form.fields[prefix + 'parcel'].choices) == 1 and \ + (prefix + 'town') in form.fields: + # only the empty choice is available + form.fields.pop(prefix + 'parcel') + form.fields[prefix + 'town'].required = True + if (prefix + 'town') in form.fields: + if form.fields[prefix + 'town'].required: + form.fields[prefix + 'town'].choices = [] # remove the empty choice + form.fields[prefix + 'town'].choices += [ + (t.pk, str(t)) for t in operation.towns.all()] + + class RecordFormGeneral(CustomForm, ManageOldType): HEADERS = {} form_label = _("General") @@ -279,25 +300,7 @@ class RecordFormGeneral(CustomForm, ManageOldType): if operation: self.fields['operation_id'].initial = operation.pk - parcels = operation.parcels.all() - sort = lambda x: (x.town.name, x.section) - parcels = sorted(parcels, key=sort) - for key, gparcels in groupby(parcels, sort): - self.fields['parcel'].choices.append( - (" - ".join([k for k in key if k]), - [(parcel.pk, parcel.short_label) for parcel in gparcels]) - ) - if len(self.fields['parcel'].choices) == 1 and \ - 'town' in self.fields: - # only the empty choice is available - self.fields.pop('parcel') - self.fields['town'].required = True - if 'town' in self.fields: - if self.fields['town'].required: - self.fields['town'].choices = [] # remove the empty choice - self.fields['town'].choices += [(t.pk, str(t)) - for t in operation.towns.all()] - + get_init_parcel(self, operation) self.fields['archaeological_site'].choices += [ (site.pk, str(site)) for site in operation.archaeological_sites.all() @@ -478,3 +481,67 @@ class QAOperationCR(IshtarForm): else: data["town_id"] = self.cleaned_data['town'] models.ContextRecord.objects.create(**data) + + +class QAContextRecordDuplicateForm(IshtarForm): + qa_label = forms.CharField(label=_("ID"), max_length=None, required=True) + qa_parcel = forms.ChoiceField(label=_(u"Parcel"), choices=[]) + qa_town = forms.ChoiceField(label=_(u"Town"), choices=[], required=False) + qa_unit = forms.ChoiceField(label=_(u"Context record type"), required=False, + choices=[]) + + TYPES = [ + FieldType('qa_unit', models.Unit), + ] + + def __init__(self, *args, **kwargs): + self.user = None + if 'user' in kwargs: + self.user = kwargs.pop('user') + if hasattr(self.user, 'ishtaruser'): + self.user = self.user.ishtaruser + self.context_record = kwargs.pop('items')[0] + super(QAContextRecordDuplicateForm, self).__init__(*args, **kwargs) + + profile = IshtarSiteProfile.get_current_profile() + self.fields['qa_parcel'].choices = [('', '--')] + if not profile.parcel_mandatory: + self.fields['qa_parcel'].required = False + self.fields['qa_town'].choices = [('', '--')] + else: + self.fields.pop('qa_town') + + get_init_parcel(self, self.context_record.operation, prefix="qa_") + + if "qa_town" in self.fields and self.context_record.town: + self.fields["qa_town"].initial = self.context_record.town.pk + if "qa_parcel" in self.fields and self.context_record.parcel: + self.fields["qa_parcel"].initial = self.context_record.parcel.pk + + self.fields['qa_label'].initial = ( + self.context_record.label or "") + str(_(" - duplicate")) + if self.context_record.unit: + self.fields["qa_unit"].initial = self.context_record.unit.pk + + def save(self): + data = {"label": self.cleaned_data["qa_label"]} + if self.cleaned_data.get("qa_unit", None): + try: + data["unit"] = models.Unit.objects.get(pk=int( + self.cleaned_data["qa_unit"]), available=True) + except models.Unit.DoesNotExist: + pass + if self.cleaned_data.get("qa_town", None): + try: + data["town"] = Town.objects.get(pk=int( + self.cleaned_data["qa_town"])) + except Town.DoesNotExist: + pass + if self.cleaned_data.get("qa_parcel", None): + try: + data["parcel"] = Parcel.objects.get(pk=int( + self.cleaned_data["qa_parcel"])) + except Parcel.DoesNotExist: + pass + return self.context_record.duplicate( + self.user, data=data) diff --git a/archaeological_context_records/models.py b/archaeological_context_records/models.py index febc7cfdd..9b1483e0d 100644 --- a/archaeological_context_records/models.py +++ b/archaeological_context_records/models.py @@ -459,7 +459,11 @@ class ContextRecord(BulkUpdatedItem, DocumentItem, BaseHistorizedItem, rights=['change_contextrecord', 'change_own_contextrecord'] ) QUICK_ACTIONS = [ - QA_LOCK + QA_LOCK, + QuickAction( + url="contextrecord-qa-duplicate", icon_class="fa fa-clone", + text=_(u"Duplicate"), target="one", + rights=['change_contextrecord', 'change_own_contextrecord']), ] history = HistoricalRecords(bases=[HistoryModel]) @@ -723,6 +727,19 @@ class ContextRecord(BulkUpdatedItem, DocumentItem, BaseHistorizedItem, def show_url(self): return reverse('show-contextrecord', args=[self.pk, '']) + def get_extra_actions(self, request): + # url, base_text, icon, extra_text, extra css class, is a quick action + actions = super(ContextRecord, self).get_extra_actions(request) + + #is_locked = hasattr(self, "is_locked") and self.is_locked(request.user) + can_edit_cr = self.can_do(request, 'change_contextrecord') + if can_edit_cr: + actions += [ + (reverse("contextrecord-qa-duplicate", args=[self.pk]), + _("Duplicate"), "fa fa-clone", "", "", True), + ] + return actions + @classmethod def get_query_owns(cls, ishtaruser): q = cls._construct_query_own( diff --git a/archaeological_context_records/templates/ishtar/forms/qa_contextrecord_duplicate.html b/archaeological_context_records/templates/ishtar/forms/qa_contextrecord_duplicate.html new file mode 100644 index 000000000..a018a67fa --- /dev/null +++ b/archaeological_context_records/templates/ishtar/forms/qa_contextrecord_duplicate.html @@ -0,0 +1,39 @@ +{% extends "ishtar/forms/qa_base.html" %} +{% load i18n inline_formset table_form %} + +{% block main_form %} +{% if form.non_field_errors %} +<div class="alert alert-danger" role="alert"> + {{form.non_field_errors}} +</div> +{% endif %} +<div class="form-row"> + <div class="form-group col-lg-12 required full-width"> + <label>{% trans "Operation" %}{% trans ":" %}</label> + {{operation}} + </div> +</div> +{% with force_large_col=True %} +<div class="form-row"> + {% with form.qa_label as field %} + {% include "blocks/bs_field_snippet.html" %} + {% endwith %} +</div> +<div class="form-row"> + {% with form.qa_parcel as field %} + {% include "blocks/bs_field_snippet.html" %} + {% endwith %} +</div> +{% if form.qa_town %}<div class="form-row"> + {% with form.qa_town as field %} + {% include "blocks/bs_field_snippet.html" %} + {% endwith %} +</div>{% endif %} +{% if form.qa_parcel %}<div class="form-row"> + {% with form.qa_unit as field %} + {% include "blocks/bs_field_snippet.html" %} + {% endwith %} +</div>{% endif %} +{% endwith %} +{% endblock %} + diff --git a/archaeological_context_records/templates/ishtar/sheet_contextrecord.html b/archaeological_context_records/templates/ishtar/sheet_contextrecord.html index a7d01c6dd..ffeba676c 100644 --- a/archaeological_context_records/templates/ishtar/sheet_contextrecord.html +++ b/archaeological_context_records/templates/ishtar/sheet_contextrecord.html @@ -104,7 +104,6 @@ <div class="tab-pane fade show active" id="{{window_id}}-identification" role="tabpanel" aria-labelledby="{{window_id}}-identification-tab"> - {% if has_image %} <div class="clearfix"> <div class="card float-left col-12 col-md-6 col-lg-4"> {% include "ishtar/blocks/window_image.html" %} @@ -116,17 +115,10 @@ </p> </div> </div> - </div> - {% else %} - <div> - <p class='window-refs'>{{ item.parcel.short_label }}</p> - <p class="window-refs">{{ item.label|default:"" }}</p> - {% include "ishtar/blocks/sheet_external_id.html" %} - </div> - {% endif %} - <div class='row'> - {% field_flex_2 "Complete ID" item.full_label %} - {% field_flex_2 "Type" item.unit %} + <div class='row'> + {% field_flex_2 "Complete ID" item.full_label %} + {% field_flex_2 "Type" item.unit %} + </div> </div> <h3>{% trans "Description"%}</h3> <div class="row"> diff --git a/archaeological_context_records/urls.py b/archaeological_context_records/urls.py index d5c2c7d7d..434aa79f9 100644 --- a/archaeological_context_records/urls.py +++ b/archaeological_context_records/urls.py @@ -95,5 +95,9 @@ urlpatterns = [ url(r'^contextrecord-qa-lock/(?P<pks>[0-9-]+)?/$', views.QAContextRecordLockView.as_view(), name='contextrecord-qa-lock', kwargs={"model": models.ContextRecord}), + url(r'^contextrecord-qa-duplicate/(?P<pks>[0-9-]+)?/$', + check_rights(['change_contextrecord', 'change_own_contextrecord'])( + views.QAContextRecordDuplicateFormView.as_view()), + name='contextrecord-qa-duplicate'), ] diff --git a/archaeological_context_records/views.py b/archaeological_context_records/views.py index 74def46a4..215dd2b4a 100644 --- a/archaeological_context_records/views.py +++ b/archaeological_context_records/views.py @@ -233,3 +233,26 @@ class QAContextRecordLockView(QABaseLockView): model = models.ContextRecord base_url = "contextrecord-qa-lock" + +class QAContextRecordDuplicateFormView(QAItemForm): + template_name = 'ishtar/forms/qa_contextrecord_duplicate.html' + model = models.ContextRecord + page_name = _("Duplicate") + form_class = forms.QAContextRecordDuplicateForm + base_url = "contextrecord-qa-duplicate" + + def get_form_kwargs(self): + kwargs = super(QAContextRecordDuplicateFormView, self).get_form_kwargs() + kwargs['user'] = self.request.user + return kwargs + + def form_valid(self, form): + form.save() + return HttpResponseRedirect(reverse("success")) + + def get_context_data(self, **kwargs): + data = super(QAContextRecordDuplicateFormView, self).get_context_data( + **kwargs) + data['action_name'] = _(u"Duplicate") + data["operation"] = self.items[0].operation + return data diff --git a/archaeological_finds/forms.py b/archaeological_finds/forms.py index babc56876..bf5854cc6 100644 --- a/archaeological_finds/forms.py +++ b/archaeological_finds/forms.py @@ -770,7 +770,7 @@ class QAFindDuplicateForm(IshtarForm): self.find = kwargs.pop('items')[0] super(QAFindDuplicateForm, self).__init__(*args, **kwargs) self.fields['label'].initial = self.find.label + str( - _(u" - duplicate")) + _(" - duplicate")) self.fields['denomination'].initial = self.find.denomination or "" def save(self): diff --git a/archaeological_operations/forms.py b/archaeological_operations/forms.py index 18534fe9d..eab5de15b 100644 --- a/archaeological_operations/forms.py +++ b/archaeological_operations/forms.py @@ -1876,3 +1876,91 @@ class QAOperationFormMulti(QAForm): return "" return value + +class QAOperationDuplicateForm(IshtarForm): + qa_code_patriarche = forms.CharField( + max_length=500, widget=OAWidget, label=_("Code PATRIARCHE"), + required=False) + qa_year = forms.IntegerField(label=_("Year"), required=False, + validators=[validators.MinValueValidator(1000), + validators.MaxValueValidator(2100)]) + qa_common_name = forms.CharField(label=_("Generic name"), required=False, + max_length=500, widget=forms.Textarea) + qa_operation_type = forms.ChoiceField(label=_("Operation type"), choices=[]) + + TYPES = [ + FieldType('qa_operation_type', models.OperationType), + ] + + def __init__(self, *args, **kwargs): + self.user = None + if 'user' in kwargs: + self.user = kwargs.pop('user') + if hasattr(self.user, 'ishtaruser'): + self.user = self.user.ishtaruser + self.operation = kwargs.pop('items')[0] + super(QAOperationDuplicateForm, self).__init__(*args, **kwargs) + + self.fields['qa_year'].initial = self.operation.year + self.fields['qa_common_name'].initial = self.operation.common_name + + self.fields["qa_operation_type"].initial = \ + self.operation.operation_type.pk + + def clean_qa_code_patriarche(self): + code = self.cleaned_data['qa_code_patriarche'] + if models.Operation.objects \ + .filter(code_patriarche=code).count(): + raise forms.ValidationError(_("This code already exists.")) + return code + + def save(self): + data = {"operation_code": None} + for k in ("code_patriarche", "common_name", "year"): + data[k] = self.cleaned_data.get("qa_" + k, None) + try: + data["operation_type"] = models.OperationType.objects.get( + pk=self.cleaned_data["qa_operation_type"], available=True + ) + except models.OperationType.DoesNotExist: + return + operation = self.operation.duplicate(self.user, data=data) + # clear associated sites + operation.archaeological_sites.clear() + operation.skip_history_when_saving = True + operation._cached_label_checked = False + operation._search_updated = False + operation._no_move = True + operation.save() # regen of labels + return operation + + +class QAArchaeologicalSiteDuplicateForm(IshtarForm): + qa_reference = forms.CharField(label=_("Reference"), max_length=200) + qa_name = forms.CharField(label=_("Name"), max_length=200, required=False) + + def __init__(self, *args, **kwargs): + self.user = None + if 'user' in kwargs: + self.user = kwargs.pop('user') + if hasattr(self.user, 'ishtaruser'): + self.user = self.user.ishtaruser + self.site = kwargs.pop('items')[0] + super(QAArchaeologicalSiteDuplicateForm, self).__init__(*args, **kwargs) + + self.fields['qa_reference'].initial = ( + self.site.reference or "") + str(_(" - duplicate")) + self.fields['qa_name'].initial = self.site.name + + def clean_qa_reference(self): + reference = self.cleaned_data['qa_reference'] + if models.ArchaeologicalSite.objects \ + .filter(reference=reference).count(): + raise forms.ValidationError(_("This reference already exists.")) + return reference + + def save(self): + data = {} + for k in ("name", "reference"): + data[k] = self.cleaned_data.get("qa_" + k, None) + return self.site.duplicate(self.user, data=data) diff --git a/archaeological_operations/models.py b/archaeological_operations/models.py index 402f45485..54a44999c 100644 --- a/archaeological_operations/models.py +++ b/archaeological_operations/models.py @@ -283,7 +283,12 @@ class ArchaeologicalSite(DocumentItem, BaseHistorizedItem, QRCodeItem, rights=['change_operation', 'change_own_operation'] ) QUICK_ACTIONS = [ - QA_LOCK + QA_LOCK, + QuickAction( + url="site-qa-duplicate", icon_class="fa fa-clone", + text=_("Duplicate"), target="one", + rights=['change_archaeologicalsite', + 'change_own_archaeologicalsite']), ] objects = SiteManager() @@ -409,6 +414,22 @@ class ArchaeologicalSite(DocumentItem, BaseHistorizedItem, QRCodeItem, return Find.objects.filter( base_finds__context_record__archaeological_site__pk=self.pk) + def get_extra_actions(self, request): + """ + For sheet template + """ + # url, base_text, icon, extra_text, extra css class, is a quick action + actions = super(ArchaeologicalSite, self).get_extra_actions(request) + # is_locked = self.is_locked(request.user) + + can_edit_site = self.can_do(request, 'change_archaeologicalsite') + if can_edit_site: + actions += [ + (reverse("site-qa-duplicate", args=[self.pk]), + _("Duplicate"), "fa fa-clone", "", "", True), + ] + return actions + @classmethod def _get_query_owns_dicts(cls, ishtaruser, no_rel=False): profile = ishtaruser.current_profile @@ -932,7 +953,11 @@ class Operation(ClosedItem, DocumentItem, BaseHistorizedItem, QRCodeItem, rights=['change_operation', 'change_own_operation'] ) QUICK_ACTIONS = [ - QA_EDIT, QA_LOCK + QA_EDIT, QA_LOCK, + QuickAction( + url="operation-qa-duplicate", icon_class="fa fa-clone", + text=_("Duplicate"), target="one", + rights=['change_operation', 'change_own_operation']), ] UP_MODEL_QUERY = { @@ -1352,9 +1377,16 @@ class Operation(ClosedItem, DocumentItem, BaseHistorizedItem, QRCodeItem, """ # url, base_text, icon, extra_text, extra css class, is a quick action actions = super(Operation, self).get_extra_actions(request) + is_locked = self.is_locked(request.user) + can_edit_operation = self.can_do(request, 'change_operation') + if can_edit_operation: + actions += [ + (reverse("operation-qa-duplicate", args=[self.pk]), + _("Duplicate"), "fa fa-clone", "", "", True), + ] can_add_cr = self.can_do(request, 'add_contextrecord') - if can_add_cr and not self.is_locked(request.user): + if can_add_cr and not is_locked: actions += [ (reverse('operation-qa-contextrecord', args=[self.pk]), _("Add context record"), "fa fa-plus", diff --git a/archaeological_operations/templates/ishtar/forms/qa_operation_duplicate.html b/archaeological_operations/templates/ishtar/forms/qa_operation_duplicate.html new file mode 100644 index 000000000..3c07cdf65 --- /dev/null +++ b/archaeological_operations/templates/ishtar/forms/qa_operation_duplicate.html @@ -0,0 +1,39 @@ +{% extends "ishtar/forms/qa_base.html" %} +{% load i18n inline_formset table_form %} + +{% block main_form %} +{% if form.non_field_errors %} +<div class="alert alert-danger" role="alert"> + {{form.non_field_errors}} +</div> +{% endif %} +<div class="form-row"> + {% with form.qa_code_patriarche as field %} + {% include "blocks/bs_field_snippet.html" %} + {% endwith %} +</div> +{% with force_large_col=True %} +<div class="form-row"> + {% with form.qa_year as field %} + {% include "blocks/bs_field_snippet.html" %} + {% endwith %} +</div> +<div class="form-row"> + {% with form.qa_common_name as field %} + {% include "blocks/bs_field_snippet.html" %} + {% endwith %} +</div> +<div class="form-row"> + {% with form.qa_operation_type as field %} + {% include "blocks/bs_field_snippet.html" %} + {% endwith %} +</div> +{% endwith %} +{% comment %} +<p> + <label for="id_modify">{% trans "Edit the duplicated find" %}{% trans ":" %}</label> + <input type="checkbox" name="modify" id="id_modify"> +</p> +{% endcomment %} +{% endblock %} + diff --git a/archaeological_operations/templates/ishtar/forms/qa_site_duplicate.html b/archaeological_operations/templates/ishtar/forms/qa_site_duplicate.html new file mode 100644 index 000000000..3affec502 --- /dev/null +++ b/archaeological_operations/templates/ishtar/forms/qa_site_duplicate.html @@ -0,0 +1,23 @@ +{% extends "ishtar/forms/qa_base.html" %} +{% load i18n inline_formset table_form %} + +{% block main_form %} +{% if form.non_field_errors %} +<div class="alert alert-danger" role="alert"> + {{form.non_field_errors}} +</div> +{% endif %} +{% with force_large_col=True %} +<div class="form-row"> + {% with form.qa_reference as field %} + {% include "blocks/bs_field_snippet.html" %} + {% endwith %} +</div> +<div class="form-row"> + {% with form.qa_name as field %} + {% include "blocks/bs_field_snippet.html" %} + {% endwith %} +</div> +{% endwith %} +{% endblock %} + diff --git a/archaeological_operations/urls.py b/archaeological_operations/urls.py index a52aab3de..a1e658c04 100644 --- a/archaeological_operations/urls.py +++ b/archaeological_operations/urls.py @@ -185,11 +185,20 @@ urlpatterns = [ check_rights(['change_operation', 'change_own_operation'])( views.QAOperationForm.as_view()), name='operation-qa-bulk-update-confirm', kwargs={"confirm": True}), + url(r'^operation-qa-duplicate/(?P<pks>[0-9-]+)?/$', + check_rights(['change_operation', 'change_own_operation'])( + views.QAOperationdDuplicateFormView.as_view()), + name='operation-qa-duplicate'), url(r'^operation-qa-lock/(?P<pks>[0-9-]+)?/$', views.QAOperationLockView.as_view(), name='operation-qa-lock', kwargs={"model": models.Operation}), + url(r'^site-qa-duplicate/(?P<pks>[0-9-]+)?/$', + check_rights(['change_archaeologicalsite', + 'change_own_archaeologicalsite'])( + views.QAArchaeologicalSiteDuplicateFormView.as_view()), + name='site-qa-duplicate'), url(r'^site-qa-lock/(?P<pks>[0-9-]+)?/$', views.QASiteLockView.as_view(), name='site-qa-lock', kwargs={"model": models.ArchaeologicalSite}), diff --git a/archaeological_operations/views.py b/archaeological_operations/views.py index 38280ccbf..657ee7b1e 100644 --- a/archaeological_operations/views.py +++ b/archaeological_operations/views.py @@ -28,33 +28,15 @@ from django.shortcuts import render, redirect from ishtar_common.utils import ugettext_lazy as _, pgettext_lazy from archaeological_operations import models -from archaeological_operations.forms import ArchaeologicalSiteForm, \ - OperationFormSelection, OperationFormFileChoice, OperationFormGeneral, \ - OperationFormModifGeneral, FinalOperationClosingForm, \ - CollaboratorForm, ArchaeologicalSiteFormSet, OperationFormPreventive, \ - OperationFormPreventiveDiag, TownFormset, SelectedTownFormset, \ - SelectedParcelGeneralFormSet, SelectedParcelFormSet, RemainForm, \ - PeriodForm, RecordRelationsFormSet, OperationFormAbstract, \ - OperationDeletionForm, SiteFormSelection, \ - AdministrativeActOpeFormSelection, AdministrativeActOpeForm, \ - AdministrativeActOpeModifForm, FinalAdministrativeActDeleteForm, \ - AdministrativeActRegisterFormSelection, DocumentGenerationAdminActForm, \ - SiteForm, SiteTownFormset, SiteUnderwaterForm, check_underwater_module, \ - CourtOrderedSeizureForm, SiteSelect, OperationSelect, \ - QAOperationFormMulti, OperationFormMultiSelection, SiteFormMultiSelection -from archaeological_operations.wizards import has_associated_file, \ - is_preventive, is_judiciary, OperationWizard, OperationModificationWizard, \ - OperationClosingWizard, OperationDeletionWizard, SiteSearch, \ - OperationAdministrativeActWizard, OperationEditAdministrativeActWizard, \ - AdministrativeActDeletionWizard, SiteWizard, SiteModificationWizard, \ - SiteDeletionWizard, OperationSearch +from archaeological_operations import forms +from archaeological_operations import wizards from ishtar_common.forms import ClosingDateFormSelection, FinalForm, \ FinalDeleteForm from ishtar_common.models import get_current_profile, IshtarSiteProfile, \ DocumentTemplate from ishtar_common.utils import put_session_message, check_rights_condition from ishtar_common.views import gen_generate_doc, QAItemEditForm, \ - QABaseLockView, wizard_is_available + QABaseLockView, wizard_is_available, QAItemForm from ishtar_common.views_item import get_item, show_item, revert_item, \ new_qa_item from ishtar_common.wizards import SearchWizard @@ -108,7 +90,7 @@ def autocomplete_archaeologicalsite(request): new_archaeologicalsite = new_qa_item(models.ArchaeologicalSite, - ArchaeologicalSiteForm, many=True) + forms.ArchaeologicalSiteForm, many=True) def autocomplete_operation(request): @@ -159,7 +141,7 @@ def get_available_operation_code(request, year=None): get_operation = get_item(models.Operation, 'get_operation', 'operation', - search_form=OperationSelect) + search_form=forms.OperationSelect) show_operation = show_item(models.Operation, 'operation') revert_operation = revert_item(models.Operation) @@ -183,28 +165,28 @@ def dashboard_operation(request, *args, **kwargs): return render(request, 'ishtar/dashboards/dashboard_operation.html', dct) -operation_search_wizard = OperationSearch.as_view( - [('general-operation_search', OperationFormSelection)], +operation_search_wizard = wizards.OperationSearch.as_view( + [('general-operation_search', forms.OperationFormSelection)], label=_(u"Operation search"), url_name='operation_search',) wizard_steps = [ - ('filechoice-operation_creation', OperationFormFileChoice), - ('general-operation_creation', OperationFormGeneral), - ('judiciary-operation_creation', CourtOrderedSeizureForm), - ('collaborators-operation_creation', CollaboratorForm), - ('archaeologicalsite-operation_creation', ArchaeologicalSiteFormSet), - ('preventive-operation_creation', OperationFormPreventive), - ('preventivediag-operation_creation', OperationFormPreventiveDiag), - ('townsgeneral-operation_creation', TownFormset), - ('towns-operation_creation', SelectedTownFormset), - ('parcelsgeneral-operation_creation', SelectedParcelGeneralFormSet), - ('parcels-operation_creation', SelectedParcelFormSet), - ('remains-operation_creation', RemainForm), - ('periods-operation_creation', PeriodForm), - ('relations-operation_creation', RecordRelationsFormSet), - ('abstract-operation_creation', OperationFormAbstract), + ('filechoice-operation_creation', forms.OperationFormFileChoice), + ('general-operation_creation', forms.OperationFormGeneral), + ('judiciary-operation_creation', forms.CourtOrderedSeizureForm), + ('collaborators-operation_creation', forms.CollaboratorForm), + ('archaeologicalsite-operation_creation', forms.ArchaeologicalSiteFormSet), + ('preventive-operation_creation', forms.OperationFormPreventive), + ('preventivediag-operation_creation', forms.OperationFormPreventiveDiag), + ('townsgeneral-operation_creation', forms.TownFormset), + ('towns-operation_creation', forms.SelectedTownFormset), + ('parcelsgeneral-operation_creation', forms.SelectedParcelGeneralFormSet), + ('parcels-operation_creation', forms.SelectedParcelFormSet), + ('remains-operation_creation', forms.RemainForm), + ('periods-operation_creation', forms.PeriodForm), + ('relations-operation_creation', forms.RecordRelationsFormSet), + ('abstract-operation_creation', forms.OperationFormAbstract), ('final-operation_creation', FinalForm)] @@ -224,49 +206,51 @@ check_files_for_operation = get_check_files_for_operation() ope_crea_condition_dict = { 'filechoice-operation_creation': check_files_for_operation, - 'judiciary-operation_creation': is_judiciary( + 'judiciary-operation_creation': wizards.is_judiciary( 'general-operation_creation', models.OperationType, 'operation_type', ), 'preventive-operation_creation': get_check_files_for_operation( - is_preventive('general-operation_creation', models.OperationType, - 'operation_type', 'prev_excavation')), + wizards.is_preventive('general-operation_creation', + models.OperationType, 'operation_type', + 'prev_excavation')), 'preventivediag-operation_creation': get_check_files_for_operation( - is_preventive('general-operation_creation', models.OperationType, - 'operation_type', 'arch_diagnostic')), - 'townsgeneral-operation_creation': has_associated_file( + wizards.is_preventive('general-operation_creation', + models.OperationType, 'operation_type', + 'arch_diagnostic')), + 'townsgeneral-operation_creation': wizards.has_associated_file( 'filechoice-operation_creation', negate=True), - 'towns-operation_creation': has_associated_file( + 'towns-operation_creation': wizards.has_associated_file( 'filechoice-operation_creation'), - 'parcelsgeneral-operation_creation': has_associated_file( + 'parcelsgeneral-operation_creation': wizards.has_associated_file( 'filechoice-operation_creation', negate=True), - 'parcels-operation_creation': has_associated_file( + 'parcels-operation_creation': wizards.has_associated_file( 'filechoice-operation_creation'), } -operation_creation_wizard = OperationWizard.as_view( +operation_creation_wizard = wizards.OperationWizard.as_view( wizard_steps, label=_(u"New operation"), condition_dict=ope_crea_condition_dict, url_name='operation_creation',) operation_modif_wizard_steps = [ - ('selec-operation_modification', OperationFormSelection), - ('general-operation_modification', OperationFormModifGeneral), - ('judiciary-operation_modification', CourtOrderedSeizureForm), - ('collaborators-operation_modification', CollaboratorForm), - ('archaeologicalsite-operation_modification', ArchaeologicalSiteFormSet), - ('preventive-operation_modification', OperationFormPreventive), - ('preventivediag-operation_modification', OperationFormPreventiveDiag), - ('towns-operation_modification', SelectedTownFormset), - ('townsgeneral-operation_modification', TownFormset), - ('parcels-operation_modification', SelectedParcelFormSet), - ('parcelsgeneral-operation_modification', SelectedParcelGeneralFormSet), - ('remains-operation_modification', RemainForm), - ('periods-operation_modification', PeriodForm), - ('relations-operation_modification', RecordRelationsFormSet), - ('abstract-operation_modification', OperationFormAbstract), + ('selec-operation_modification', forms.OperationFormSelection), + ('general-operation_modification', forms.OperationFormModifGeneral), + ('judiciary-operation_modification', forms.CourtOrderedSeizureForm), + ('collaborators-operation_modification', forms.CollaboratorForm), + ('archaeologicalsite-operation_modification', forms.ArchaeologicalSiteFormSet), + ('preventive-operation_modification', forms.OperationFormPreventive), + ('preventivediag-operation_modification', forms.OperationFormPreventiveDiag), + ('towns-operation_modification', forms.SelectedTownFormset), + ('townsgeneral-operation_modification', forms.TownFormset), + ('parcels-operation_modification', forms.SelectedParcelFormSet), + ('parcelsgeneral-operation_modification', forms.SelectedParcelGeneralFormSet), + ('remains-operation_modification', forms.RemainForm), + ('periods-operation_modification', forms.PeriodForm), + ('relations-operation_modification', forms.RecordRelationsFormSet), + ('abstract-operation_modification', forms.OperationFormAbstract), ('final-operation_modification', FinalForm) ] @@ -274,27 +258,29 @@ operation_modif_wizard_steps = [ ope_modif_condition_dict = { 'preventive-operation_modification': get_check_files_for_operation( - is_preventive('general-operation_modification', models.OperationType, - 'operation_type', 'prev_excavation')), + wizards.is_preventive('general-operation_modification', + models.OperationType, 'operation_type', + 'prev_excavation')), 'preventivediag-operation_modification': get_check_files_for_operation( - is_preventive('general-operation_modification', models.OperationType, - 'operation_type', 'arch_diagnostic')), - 'judiciary-operation_modification': is_judiciary( + wizards.is_preventive('general-operation_modification', + models.OperationType, 'operation_type', + 'arch_diagnostic')), + 'judiciary-operation_modification': wizards.is_judiciary( 'general-operation_modification', models.OperationType, 'operation_type', ), - 'townsgeneral-operation_modification': has_associated_file( + 'townsgeneral-operation_modification': wizards.has_associated_file( 'general-operation_modification', negate=True), - 'towns-operation_modification': has_associated_file( + 'towns-operation_modification': wizards.has_associated_file( 'general-operation_modification'), - 'parcelsgeneral-operation_modification': has_associated_file( + 'parcelsgeneral-operation_modification': wizards.has_associated_file( 'general-operation_modification', negate=True), - 'parcels-operation_modification': has_associated_file( + 'parcels-operation_modification': wizards.has_associated_file( 'general-operation_modification'), } -operation_modification_wizard = OperationModificationWizard.as_view( +operation_modification_wizard = wizards.OperationModificationWizard.as_view( operation_modif_wizard_steps, label=_(u"Operation modification"), condition_dict=ope_modif_condition_dict, @@ -307,7 +293,7 @@ def operation_modify(request, pk): return HttpResponseRedirect("/") wizard_url = 'operation_modification' - OperationModificationWizard.session_set_value( + wizards.OperationModificationWizard.session_set_value( request, 'selec-' + wizard_url, 'pk', pk, reset=True) return redirect(reverse(wizard_url, kwargs={'step': 'general-' + wizard_url})) @@ -315,29 +301,29 @@ def operation_modify(request, pk): def operation_add(request, file_id): operation_creation_wizard(request) - OperationWizard.session_set_value( + wizards.OperationWizard.session_set_value( request, 'filechoice-operation_creation', 'associated_file', file_id, reset=True) return redirect(reverse('operation_creation', kwargs={'step': 'general-operation_creation'})) operation_closing_steps = [ - ('selec-operation_closing', OperationFormSelection), + ('selec-operation_closing', forms.OperationFormSelection), ('date-operation_closing', ClosingDateFormSelection), - ('final-operation_closing', FinalOperationClosingForm)] + ('final-operation_closing', forms.FinalOperationClosingForm)] -operation_closing_wizard = OperationClosingWizard.as_view( +operation_closing_wizard = wizards.OperationClosingWizard.as_view( operation_closing_steps, label=_(u"Operation closing"), url_name='operation_closing',) operation_deletion_steps = [ - ('selec-operation_deletion', OperationFormMultiSelection), - ('final-operation_deletion', OperationDeletionForm) + ('selec-operation_deletion', forms.OperationFormMultiSelection), + ('final-operation_deletion', forms.OperationDeletionForm) ] -operation_deletion_wizard = OperationDeletionWizard.as_view( +operation_deletion_wizard = wizards.OperationDeletionWizard.as_view( operation_deletion_steps, label=_(u"Operation deletion"), url_name='operation_deletion',) @@ -349,7 +335,7 @@ def operation_delete(request, pk): return HttpResponseRedirect("/") wizard_url = 'operation_deletion' - OperationDeletionWizard.session_set_value( + wizards.OperationDeletionWizard.session_set_value( request, 'selec-' + wizard_url, 'pks', pk, reset=True) return redirect(reverse(wizard_url, kwargs={'step': 'final-' + wizard_url})) @@ -362,43 +348,44 @@ def site_extra_context(request, item): get_site = get_item(models.ArchaeologicalSite, 'get_site', 'site', - search_form=SiteSelect) + search_form=forms.SiteSelect) show_site = show_item( models.ArchaeologicalSite, 'site', extra_dct=site_extra_context ) revert_site = revert_item(models.ArchaeologicalSite) -site_search_wizard = SiteSearch.as_view( - [('general-site_search', SiteFormSelection)], +site_search_wizard = wizards.SiteSearch.as_view( + [('general-site_search', forms.SiteFormSelection)], url_name='site_search', ) site_creation_steps = [ - ('general-site_creation', SiteForm), - ('towns-site_creation', SiteTownFormset), - ('underwater-site_creation', SiteUnderwaterForm), + ('general-site_creation', forms.SiteForm), + ('towns-site_creation', forms.SiteTownFormset), + ('underwater-site_creation', forms.SiteUnderwaterForm), ('final-site_creation', FinalForm) ] -site_creation_wizard = SiteWizard.as_view( +site_creation_wizard = wizards.SiteWizard.as_view( site_creation_steps, - condition_dict={'underwater-site_creation': check_underwater_module}, + condition_dict={'underwater-site_creation': forms.check_underwater_module}, url_name='site_creation', ) site_modification_steps = [ - ('selec-site_modification', SiteFormSelection), - ('general-site_modification', SiteForm), - ('towns-site_modification', SiteTownFormset), - ('underwater-site_modification', SiteUnderwaterForm), + ('selec-site_modification', forms.SiteFormSelection), + ('general-site_modification', forms.SiteForm), + ('towns-site_modification', forms.SiteTownFormset), + ('underwater-site_modification', forms.SiteUnderwaterForm), ('final-site_modification', FinalForm) ] -site_modification_wizard = SiteModificationWizard.as_view( +site_modification_wizard = wizards.SiteModificationWizard.as_view( site_modification_steps, - condition_dict={'underwater-site_modification': check_underwater_module}, + condition_dict={ + 'underwater-site_modification': forms.check_underwater_module}, url_name='site_modification', ) @@ -408,18 +395,18 @@ def site_modify(request, pk): models.ArchaeologicalSite, pk): return HttpResponseRedirect("/") wizard_url = 'site_modification' - SiteModificationWizard.session_set_value( + wizards.SiteModificationWizard.session_set_value( request, 'selec-' + wizard_url, 'pk', pk, reset=True) return redirect(reverse(wizard_url, kwargs={'step': 'general-' + wizard_url})) site_deletion_steps = [ - ('selec-site_deletion', SiteFormMultiSelection), + ('selec-site_deletion', forms.SiteFormMultiSelection), ('final-site_deletion', FinalDeleteForm) ] -site_deletion_wizard = SiteDeletionWizard.as_view( +site_deletion_wizard = wizards.SiteDeletionWizard.as_view( site_deletion_steps, label=_(u"Site deletion"), url_name='site_deletion',) @@ -430,38 +417,38 @@ def site_delete(request, pk): models.ArchaeologicalSite, pk): return HttpResponseRedirect("/") wizard_url = 'site_deletion' - SiteDeletionWizard.session_set_value( + wizards.SiteDeletionWizard.session_set_value( request, 'selec-' + wizard_url, 'pks', pk, reset=True) return redirect(reverse(wizard_url, kwargs={'step': 'final-' + wizard_url})) -operation_administrativeactop_search_wizard = SearchWizard.as_view([ +operation_administrativeactop_search_wizard = wizards.SearchWizard.as_view([ ('general-operation_administrativeactop_search', - AdministrativeActOpeFormSelection)], + forms.AdministrativeActOpeFormSelection)], label=_(u"Administrative act search"), url_name='operation_administrativeactop_search',) administrativeactop_steps = [ - ('selec-operation_administrativeactop', OperationFormSelection), + ('selec-operation_administrativeactop', forms.OperationFormSelection), ('administrativeact-operation_administrativeactop', - AdministrativeActOpeForm), + forms.AdministrativeActOpeForm), ('final-operation_administrativeactop', FinalForm)] operation_administrativeactop_wizard = \ - OperationAdministrativeActWizard.as_view( + wizards.OperationAdministrativeActWizard.as_view( administrativeactop_steps, label=_(u"Operation: new administrative act"), url_name='operation_administrativeactop',) operation_administrativeactop_modification_wizard = \ - OperationEditAdministrativeActWizard.as_view([ + wizards.OperationEditAdministrativeActWizard.as_view([ ('selec-operation_administrativeactop_modification', - AdministrativeActOpeFormSelection), + forms.AdministrativeActOpeFormSelection), ('administrativeact-operation_administrativeactop_modification', - AdministrativeActOpeModifForm), + forms.AdministrativeActOpeModifForm), ('final-operation_administrativeactop_modification', FinalForm)], - label=_(u"Operation: administrative act modification"), + label=_("Operation: administrative act modification"), url_name='operation_administrativeactop_modification',) @@ -471,18 +458,18 @@ def operation_administrativeactop_modify(request, pk): models.AdministrativeAct, pk): return HttpResponseRedirect("/") wizard_url = 'operation_administrativeactop_modification' - OperationEditAdministrativeActWizard.session_set_value( + wizards.OperationEditAdministrativeActWizard.session_set_value( request, 'selec-' + wizard_url, 'pk', pk, reset=True) return redirect( reverse(wizard_url, kwargs={'step': 'administrativeact-' + wizard_url})) operation_administrativeactop_deletion_wizard = \ - AdministrativeActDeletionWizard.as_view([ + wizards.AdministrativeActDeletionWizard.as_view([ ('selec-operation_administrativeactop_deletion', - AdministrativeActOpeFormSelection), + forms.AdministrativeActOpeFormSelection), ('final-operation_administrativeactop_deletion', - FinalAdministrativeActDeleteForm)], + forms.FinalAdministrativeActDeleteForm)], label=_(u"Operation: administrative act deletion"), url_name='operation_administrativeactop_deletion',) @@ -493,7 +480,7 @@ def operation_administrativeactop_delete(request, pk): models.AdministrativeAct, pk): return HttpResponseRedirect("/") wizard_url = 'operation_administrativeactop_deletion' - AdministrativeActDeletionWizard.session_set_value( + wizards.AdministrativeActDeletionWizard.session_set_value( request, 'selec-' + wizard_url, 'pk', pk, reset=True) return redirect( reverse(wizard_url, kwargs={'step': 'final-' + wizard_url})) @@ -501,7 +488,7 @@ def operation_administrativeactop_delete(request, pk): administrativact_register_wizard = SearchWizard.as_view([ ('general-administrativact_register', - AdministrativeActRegisterFormSelection)], + forms.AdministrativeActRegisterFormSelection)], label=pgettext_lazy('admin act register', u"Register"), url_name='administrativact_register',) @@ -511,7 +498,7 @@ generatedoc_administrativeactop = gen_generate_doc(models.AdministrativeAct) def administrativeactfile_document( request, file=False, treatment=False, treatment_file=False): - search_form = AdministrativeActOpeFormSelection + search_form = forms.AdministrativeActOpeFormSelection document_type = 'O' if file: from archaeological_files.forms import \ @@ -533,6 +520,7 @@ def administrativeactfile_document( models.AdministrativeAct): return HttpResponse(content_type='text/plain') dct = {} + DocumentGenerationAdminActForm = forms.DocumentGenerationAdminActForm if request.POST: dct['search_form'] = search_form(request.POST) dct['template_form'] = DocumentGenerationAdminActForm( @@ -578,22 +566,22 @@ def administrativeactfile_document( def reset_wizards(request): for wizard_class, url_name in ( - (OperationWizard, 'operation_creation'), - (OperationModificationWizard, 'operation_modification'), - (OperationClosingWizard, 'operation_closing'), - (OperationDeletionWizard, 'operation_deletion_wizard'), - (OperationAdministrativeActWizard, + (wizards.OperationWizard, 'operation_creation'), + (wizards.OperationModificationWizard, 'operation_modification'), + (wizards.OperationClosingWizard, 'operation_closing'), + (wizards.OperationDeletionWizard, 'operation_deletion_wizard'), + (wizards.OperationAdministrativeActWizard, 'operation_administrativeactop'), - (OperationEditAdministrativeActWizard, + (wizards.OperationEditAdministrativeActWizard, 'operation_administrativeactop_modification'), - (AdministrativeActDeletionWizard, + (wizards.AdministrativeActDeletionWizard, 'operation_administrativeactop_deletion'),): wizard_class.session_reset(request, url_name) class QAOperationForm(QAItemEditForm): model = models.Operation - form_class = QAOperationFormMulti + form_class = forms.QAOperationFormMulti class QAOperationLockView(QABaseLockView): @@ -604,3 +592,50 @@ class QAOperationLockView(QABaseLockView): class QASiteLockView(QABaseLockView): model = models.ArchaeologicalSite base_url = "site-qa-lock" + + +class QAOperationdDuplicateFormView(QAItemForm): + template_name = 'ishtar/forms/qa_operation_duplicate.html' + model = models.Operation + page_name = _("Duplicate") + form_class = forms.QAOperationDuplicateForm + base_url = "operation-qa-duplicate" + + def get_form_kwargs(self): + kwargs = super(QAOperationdDuplicateFormView, self).get_form_kwargs() + kwargs['user'] = self.request.user + return kwargs + + def form_valid(self, form): + form.save() + return HttpResponseRedirect(reverse("success")) + + def get_context_data(self, **kwargs): + data = super(QAOperationdDuplicateFormView, self).get_context_data( + **kwargs) + data['action_name'] = _("Duplicate") + return data + + +class QAArchaeologicalSiteDuplicateFormView(QAItemForm): + template_name = 'ishtar/forms/qa_site_duplicate.html' + model = models.ArchaeologicalSite + page_name = _("Duplicate") + form_class = forms.QAArchaeologicalSiteDuplicateForm + base_url = "site-qa-duplicate" + + def get_form_kwargs(self): + kwargs = super(QAArchaeologicalSiteDuplicateFormView, + self).get_form_kwargs() + kwargs['user'] = self.request.user + return kwargs + + def form_valid(self, form): + form.save() + return HttpResponseRedirect(reverse("success")) + + def get_context_data(self, **kwargs): + data = super(QAArchaeologicalSiteDuplicateFormView, + self).get_context_data(**kwargs) + data['action_name'] = _("Duplicate") + return data diff --git a/ishtar_common/forms_common.py b/ishtar_common/forms_common.py index 7a6f2fe72..31eb5c7d2 100644 --- a/ishtar_common/forms_common.py +++ b/ishtar_common/forms_common.py @@ -1302,9 +1302,9 @@ class DocumentForm(forms.ModelForm, CustomForm, ManageOldType): not cleaned_data.get('image', None) and \ not cleaned_data.get('associated_file', None) and \ not cleaned_data.get('associated_url', None): - raise forms.ValidationError(_(u"You should at least fill one of " - u"this field: title, url, image or " - u"file.")) + raise forms.ValidationError(_("You should at least fill one of " + "this field: title, url, image or " + "file.")) for rel in models.Document.RELATED_MODELS: if cleaned_data.get(rel, None): return cleaned_data @@ -1477,6 +1477,68 @@ class QADocumentFormMulti(QAForm): return value +class QADocumentDuplicateForm(IshtarForm): + qa_title = forms.CharField(label=_("Reference"), max_length=500, + required=False) + qa_source_type = forms.ChoiceField(label=_("Type"), choices=[], + required=False) + + TYPES = [ + FieldType('qa_source_type', models.SourceType), + ] + + def __init__(self, *args, **kwargs): + self.user = None + if 'user' in kwargs: + self.user = kwargs.pop('user') + if hasattr(self.user, 'ishtaruser'): + self.user = self.user.ishtaruser + self.document = kwargs.pop('items')[0] + super(QADocumentDuplicateForm, self).__init__(*args, **kwargs) + + self.fields['qa_title'].initial = self.document.title + str( + _(" - duplicate")) + if self.document.source_type: + self.fields['qa_source_type'].initial = self.document.source_type.pk + + for related_key in models.Document.RELATED_MODELS_ALT: + related = getattr(self.document, related_key) + if not related.count(): + continue + model = models.Document._meta.get_field(related_key).related_model + initial = [] + for item in related.all(): + initial.append(item.pk) + self.fields["qa_" + related_key] = widgets.Select2MultipleField( + model=model, remote=True, label=model._meta.verbose_name_plural, + required=False, long_widget=True, initial=initial + ) + + def save(self): + data = {"index": None} + for k in ["title"]: + data[k] = self.cleaned_data.get("qa_" + k, None) + if self.cleaned_data.get("qa_source_type", None): + try: + data["source_type"] = models.SourceType.objects.get( + pk=int(self.cleaned_data["qa_source_type"]), available=True) + except models.SourceType.DoesNotExist: + return + new = self.document.duplicate_item(self.user, data=data) + for related_key in models.Document.RELATED_MODELS_ALT: + getattr(new, related_key).clear() + values = self.cleaned_data.get("qa_" + related_key, []) + model = models.Document._meta.get_field(related_key).related_model + for value in values: + getattr(new, related_key).add(model.objects.get(pk=value)) + new.skip_history_when_saving = True + new._cached_label_checked = False + new._search_updated = False + new._no_move = True + new.save() # regen of labels + return new + + class QALockForm(forms.Form): action = forms.ChoiceField( label=_("Action"), choices=(('lock', _("Lock")), diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 7238d7782..dc2fef815 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -1212,7 +1212,7 @@ class HistoryError(Exception): return repr(self.value) -PRIVATE_FIELDS = ('id', 'history_modifier', 'order') +PRIVATE_FIELDS = ('id', 'history_modifier', 'order', 'uuid') class BulkUpdatedItem(object): @@ -2127,6 +2127,34 @@ class CascasdeUpdate: post_save_geo(item.__class__, instance=item) +def duplicate_item(item, user=None, data=None): + model = item.__class__ + new = model.objects.get(pk=item.pk) + + for field in model._meta.fields: + # pk is in PRIVATE_FIELDS so: new.pk = None and a new + # item will be created on save + if field.name == "uuid": + new.uuid = uuid.uuid4() + elif field.name in PRIVATE_FIELDS: + setattr(new, field.name, None) + if user: + new.history_user = user + if data: + for k in data: + setattr(new, k, data[k]) + new.save() + + # m2m fields + m2m = [field.name for field in model._meta.many_to_many + if field.name not in PRIVATE_FIELDS] + for field in m2m: + for val in getattr(item, field).all(): + if val not in getattr(new, field).all(): + getattr(new, field).add(val) + return new + + class BaseHistorizedItem(StatisticItem, TemplateItem, FullSearch, Imported, JsonData, FixAssociated, CascasdeUpdate): """ @@ -2194,29 +2222,7 @@ class BaseHistorizedItem(StatisticItem, TemplateItem, FullSearch, Imported, return {} def duplicate(self, user=None, data=None): - model = self.__class__ - new = model.objects.get(pk=self.pk) - - for field in model._meta.fields: - # pk is in PRIVATE_FIELDS so: new.pk = None and a new - # item will be created on save - if field.name in PRIVATE_FIELDS: - setattr(new, field.name, None) - if user: - new.history_user = user - if data: - for k in data: - setattr(new, k, data[k]) - new.save() - - # m2m fields - m2m = [field.name for field in model._meta.many_to_many - if field.name not in PRIVATE_FIELDS] - for field in m2m: - for val in getattr(self, field).all(): - if val not in getattr(new, field).all(): - getattr(new, field).add(val) - return new + return duplicate_item(self, user, data) def update_external_id(self, save=False): if not self.EXTERNAL_ID_KEY or ( @@ -5233,7 +5239,12 @@ class Document(BaseHistorizedItem, QRCodeItem, OwnPerms, ImageModel, text=_(u"Bulk update"), target="many", rights=['change_document', 'change_own_document']) QUICK_ACTIONS = [ - QA_EDIT + QA_EDIT, + QuickAction( + url="document-qa-duplicate", icon_class="fa fa-clone", + text=_("Duplicate"), target="one", + rights=['change_document', + 'change_own_document']), ] SERIALIZATION_FILES = ["image", "thumbnail", "associated_file"] @@ -5321,6 +5332,8 @@ class Document(BaseHistorizedItem, QRCodeItem, OwnPerms, ImageModel, return "{}-{:04d}".format(self.operation.code_patriarche or '', self.index) """ + def duplicate_item(self, user=None, data=None): + return duplicate_item(self, user, data) def public_representation(self): site = Site.objects.get_current() @@ -5352,6 +5365,22 @@ class Document(BaseHistorizedItem, QRCodeItem, OwnPerms, ImageModel, "thumbnail": thumbnail, } + def get_extra_actions(self, request): + """ + For sheet template + """ + # url, base_text, icon, extra_text, extra css class, is a quick action + actions = super(Document, self).get_extra_actions(request) + # is_locked = self.is_locked(request.user) + + can_edit_document = self.can_do(request, 'change_document') + if can_edit_document: + actions += [ + (reverse("document-qa-duplicate", args=[self.pk]), + _("Duplicate"), "fa fa-clone", "", "", True), + ] + return actions + @property def thumbnail_path(self): if not self.thumbnail: diff --git a/ishtar_common/templates/ishtar/forms/qa_document_duplicate.html b/ishtar_common/templates/ishtar/forms/qa_document_duplicate.html new file mode 100644 index 000000000..4dc04d15d --- /dev/null +++ b/ishtar_common/templates/ishtar/forms/qa_document_duplicate.html @@ -0,0 +1,95 @@ +{% extends "ishtar/forms/qa_base.html" %} +{% load i18n inline_formset table_form %} + +{% block main_form %} +{% if form.non_field_errors %} +<div class="alert alert-danger" role="alert"> + {{form.non_field_errors}} +</div> +{% endif %} +{% with force_large_col=True %} +<div class="form-row"> + {% with form.qa_title as field %} + {% include "blocks/bs_field_snippet.html" %} + {% endwith %} +</div> +<div class="form-row"> + {% with form.qa_source_type as field %} + {% include "blocks/bs_field_snippet.html" %} + {% endwith %} +</div> +{% endwith %} + +{% if form.qa_finds %} +<div class="form-row"> + {% with form.qa_finds as field %} + {% include "blocks/bs_field_snippet.html" %} + {% endwith %} +</div> +{% endif %} + +{% if form.qa_context_records %} +<div class="form-row"> + {% with form.qa_context_records as field %} + {% include "blocks/bs_field_snippet.html" %} + {% endwith %} +</div> +{% endif %} + +{% if form.qa_operations %} +<div class="form-row"> + {% with form.qa_operations as field %} + {% include "blocks/bs_field_snippet.html" %} + {% endwith %} +</div> +{% endif %} + +{% if form.qa_sites %} +<div class="form-row"> + {% with form.qa_sites as field %} + {% include "blocks/bs_field_snippet.html" %} + {% endwith %} +</div> +{% endif %} + +{% if form.qa_files %} +<div class="form-row"> + {% with form.qa_files as field %} + {% include "blocks/bs_field_snippet.html" %} + {% endwith %} +</div> +{% endif %} + +{% if form.qa_warehouses %} +<div class="form-row"> + {% with form.qa_warehouses as field %} + {% include "blocks/bs_field_snippet.html" %} + {% endwith %} +</div> +{% endif %} + +{% if form.qa_containers %} +<div class="form-row"> + {% with form.qa_containers as field %} + {% include "blocks/bs_field_snippet.html" %} + {% endwith %} +</div> +{% endif %} + +{% if form.qa_treatments %} +<div class="form-row"> + {% with form.qa_treatments as field %} + {% include "blocks/bs_field_snippet.html" %} + {% endwith %} +</div> +{% endif %} + +{% if form.qa_files %} +<div class="form-row"> + {% with form.qa_treatment_files as field %} + {% include "blocks/bs_field_snippet.html" %} + {% endwith %} +</div> +{% endif %} + +{% endblock %} diff --git a/ishtar_common/templates/ishtar/sheet_document.html b/ishtar_common/templates/ishtar/sheet_document.html index 386b33a8a..93951c056 100644 --- a/ishtar_common/templates/ishtar/sheet_document.html +++ b/ishtar_common/templates/ishtar/sheet_document.html @@ -37,7 +37,7 @@ {% field_flex "Creation date" item.creation_date %} {% field_flex "Receipt date" item.receipt_date %} {% field_flex "Receipt date in documentation" item.receipt_date_in_documentation %} - {% field_flex "Has a duplicate" item.duplicate %} + {% if item.duplicate %}{% field_flex "Has a duplicate" item.duplicate %}{% endif %} {% field_flex "Description" item.description %} {% field_flex "Comment" item.comment %} {% field_flex "Additional information" item.additional_information %} diff --git a/ishtar_common/urls.py b/ishtar_common/urls.py index 8c08ade06..cc8d3eed4 100644 --- a/ishtar_common/urls.py +++ b/ishtar_common/urls.py @@ -319,6 +319,10 @@ urlpatterns += [ check_rights(['change_document', 'change_own_document'])( views.QADocumentForm.as_view()), name='document-qa-bulk-update-confirm', kwargs={"confirm": True}), + url(r'^document-qa-duplicate/(?P<pks>[0-9-]+)?/$', + check_rights(['change_document', 'change_own_document'])( + views.QADocumentDuplicateFormView.as_view()), + name='document-qa-duplicate'), url(r'^qa-not-available(?:/(?P<context>[0-9a-z-]+))?/$', views.QANotAvailable.as_view(), name='qa-not-available'), diff --git a/ishtar_common/views.py b/ishtar_common/views.py index 3563ac9db..4724d61b7 100644 --- a/ishtar_common/views.py +++ b/ishtar_common/views.py @@ -2264,6 +2264,8 @@ class QAItemForm(IshtarMixin, LoginRequiredMixin, FormView): # check availability quick_action = self.get_quick_action() + if not quick_action: + raise Http404() if not quick_action.is_available( user=request.user, session=request.session): for item in self.items: @@ -2379,3 +2381,27 @@ class QADocumentForm(QAItemEditForm): form_class = forms.QADocumentFormMulti +class QADocumentDuplicateFormView(QAItemForm): + template_name = 'ishtar/forms/qa_document_duplicate.html' + model = models.Document + page_name = _("Duplicate") + form_class = forms.QADocumentDuplicateForm + base_url = "document-qa-duplicate" + + def get_form_kwargs(self): + kwargs = super(QADocumentDuplicateFormView, + self).get_form_kwargs() + kwargs['user'] = self.request.user + return kwargs + + def form_valid(self, form): + form.save() + return HttpResponseRedirect(reverse("success")) + + def get_context_data(self, **kwargs): + data = super(QADocumentDuplicateFormView, + self).get_context_data(**kwargs) + data['action_name'] = _("Duplicate") + return data + + diff --git a/scss/custom.scss b/scss/custom.scss index b00664552..4b0e4dfb8 100644 --- a/scss/custom.scss +++ b/scss/custom.scss @@ -116,6 +116,10 @@ pre { border: $input-border-width solid $input-border-color; } +.modal-dialog .select2-selection{ + width: 470px; +} + .page-link.imported-page { color: $ishtar-light-color; } |