localcosmos-app-kit 0.9.14__py3-none-any.whl → 0.9.16__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. app_kit/admin_urls.py +5 -0
  2. app_kit/appbuilder/AppReleaseBuilder.py +2 -2
  3. app_kit/appbuilder/TaxonBuilder.py +31 -23
  4. app_kit/appbuilder/__pycache__/AppReleaseBuilder.cpython-313.pyc +0 -0
  5. app_kit/appbuilder/__pycache__/TaxonBuilder.cpython-313.pyc +0 -0
  6. app_kit/appbuilder/app/frontends/Multiverse/settings.json +2 -2
  7. app_kit/features/nature_guides/matrix_filter_forms.py +17 -1
  8. app_kit/features/nature_guides/models.py +5 -2
  9. app_kit/features/nature_guides/templates/nature_guides/ajax/manage_matrix_filter.html +15 -8
  10. app_kit/features/nature_guides/views.py +8 -0
  11. app_kit/features/taxon_profiles/templates/taxon_profiles/ajax/delete_all_manually_added_images.html +37 -0
  12. app_kit/features/taxon_profiles/templates/taxon_profiles/ajax/manage_taxon_profile_form.html +1 -1
  13. app_kit/features/taxon_profiles/templates/taxon_profiles/manage_taxon_profiles.html +16 -0
  14. app_kit/features/taxon_profiles/tests/__pycache__/test_views.cpython-313.pyc +0 -0
  15. app_kit/features/taxon_profiles/tests/test_views.py +100 -3
  16. app_kit/features/taxon_profiles/urls.py +3 -0
  17. app_kit/features/taxon_profiles/views.py +38 -1
  18. app_kit/features/taxon_profiles/zip_import.py +4 -0
  19. app_kit/forms.py +8 -0
  20. app_kit/locale/de/LC_MESSAGES/django.mo +0 -0
  21. app_kit/locale/de/LC_MESSAGES/django.po +254 -100
  22. app_kit/taxonomy/lazy.py +6 -6
  23. app_kit/templates/app_kit/ajax/list_images_and_licences_content.html +44 -0
  24. app_kit/templates/app_kit/ajax/manage_content_licence.html +25 -0
  25. app_kit/templates/app_kit/list_images_and_licences.html +25 -0
  26. app_kit/templates/app_kit/manage_app.html +10 -1
  27. app_kit/templatetags/app_tags.py +23 -3
  28. app_kit/views.py +61 -2
  29. {localcosmos_app_kit-0.9.14.dist-info → localcosmos_app_kit-0.9.16.dist-info}/METADATA +1 -1
  30. {localcosmos_app_kit-0.9.14.dist-info → localcosmos_app_kit-0.9.16.dist-info}/RECORD +33 -29
  31. {localcosmos_app_kit-0.9.14.dist-info → localcosmos_app_kit-0.9.16.dist-info}/WHEEL +1 -1
  32. {localcosmos_app_kit-0.9.14.dist-info → localcosmos_app_kit-0.9.16.dist-info}/licenses/LICENCE +0 -0
  33. {localcosmos_app_kit-0.9.14.dist-info → localcosmos_app_kit-0.9.16.dist-info}/top_level.txt +0 -0
app_kit/admin_urls.py CHANGED
@@ -136,4 +136,9 @@ urlpatterns = [
136
136
  views.ManageAppKitExternalMedia.as_view(), name='update_app_kit_external_media'),
137
137
  path('delete-external-media/<int:meta_app_id>/<int:pk>/',
138
138
  views.DeleteAppKitExternalMedia.as_view(), name='delete_app_kit_external_media'),
139
+ # licences
140
+ path('list-images-and-licences/<int:meta_app_id>/',
141
+ views.ListImagesAndLicences.as_view(), name='list_images_and_licences'),
142
+ path('manage-content-licence/<int:meta_app_id>/<int:registry_entry_id>/',
143
+ views.ManageContentLicence.as_view(), name='manage_content_licence'),
139
144
  ]
@@ -916,10 +916,10 @@ class AppReleaseBuilder(AppBuilderBase):
916
916
 
917
917
  # detect duplicates by name
918
918
  all_taxon_profiles = TaxonProfile.objects.filter(taxon_profiles=taxon_profiles)
919
- for taxon_profile in all_taxon_profiles:
919
+ for taxon_profile in all_taxon_profiles:
920
920
  duplicates = TaxonProfile.objects.filter(taxon_profiles=taxon_profiles,
921
921
  taxon_source=taxon_profile.taxon_source, taxon_latname=taxon_profile.taxon_latname,
922
- taxon_author=taxon_profile.taxon_author).exclude(pk=taxon_profile.pk)
922
+ taxon_author=taxon_profile.taxon_author, morphotype=taxon_profile.morphotype).exclude(pk=taxon_profile.pk)
923
923
  if duplicates.exists():
924
924
  warning_message = _('The taxon profile of %(taxon_latname)s has duplicates.') % {
925
925
  'taxon_latname':taxon_profile.taxon_latname}
@@ -332,33 +332,41 @@ class TaxonSerializer:
332
332
  if name_type not in self.taxa_builder.cache['search']:
333
333
  self.taxa_builder.cache['search'][name_type] = {}
334
334
 
335
- if name in self.taxa_builder.cache['search'][name_type]:
336
- search_taxon_json = self.taxa_builder.cache['search'][name_type][name]
335
+ if name not in self.taxa_builder.cache['search'][name_type]:
336
+ # the same name might occur in different taxonomies
337
+ self.taxa_builder.cache['search'][name_type][name] = {}
337
338
 
338
- else:
339
-
340
- if not accepted_name_uuid:
341
- accepted_name_uuid = self.lazy_taxon.name_uuid
342
-
343
- if accepted_name_uuid:
344
- accepted_name_uuid = str(accepted_name_uuid)
345
-
346
- has_taxon_profile = False
347
- taxon_profile = self.get_taxon_profile()
348
- if taxon_profile:
349
- has_taxon_profile = True
339
+ if name in self.taxa_builder.cache['search'][name_type]:
340
+ # the same name might occur in different taxonomies
341
+ taxon_source = self.lazy_taxon.taxon_source
350
342
 
351
- search_taxon_json = self.serialize_extended()
343
+ if taxon_source in self.taxa_builder.cache['search'][name_type][name]:
344
+ search_taxon_json = self.taxa_builder.cache['search'][name_type][name][taxon_source]
352
345
 
353
- search_taxon_json.update({
354
- 'nameType': name_type,
355
- 'name': name,
356
- 'isPreferredName': is_preferred_name,
357
- 'acceptedNameUuid': accepted_name_uuid,
358
- 'hasTaxonProfile': has_taxon_profile,
359
- })
346
+ else:
360
347
 
361
- self.taxa_builder.cache['search'][name_type][name] = search_taxon_json
348
+ if not accepted_name_uuid:
349
+ accepted_name_uuid = self.lazy_taxon.name_uuid
350
+
351
+ if accepted_name_uuid:
352
+ accepted_name_uuid = str(accepted_name_uuid)
353
+
354
+ has_taxon_profile = False
355
+ taxon_profile = self.get_taxon_profile()
356
+ if taxon_profile:
357
+ has_taxon_profile = True
358
+
359
+ search_taxon_json = self.serialize_extended()
360
+
361
+ search_taxon_json.update({
362
+ 'nameType': name_type,
363
+ 'name': name,
364
+ 'isPreferredName': is_preferred_name,
365
+ 'acceptedNameUuid': accepted_name_uuid,
366
+ 'hasTaxonProfile': has_taxon_profile,
367
+ })
368
+
369
+ self.taxa_builder.cache['search'][name_type][name][taxon_source] = search_taxon_json
362
370
 
363
371
  search_taxon_json_copy = copy.deepcopy(search_taxon_json)
364
372
 
@@ -69,8 +69,8 @@
69
69
  },
70
70
  "cordova" : {
71
71
  "platforms" : {
72
- "android" : "android@13.0.0",
73
- "ios" : "ios@7.1.0",
72
+ "android" : "android@14.0.1",
73
+ "ios" : "ios@8.0.0",
74
74
  "browser" : "browser@7.0.0"
75
75
  },
76
76
  "plugins" : [
@@ -5,7 +5,7 @@ from django import forms
5
5
 
6
6
  from localcosmos_server.forms import LocalizeableForm
7
7
 
8
- from .models import MatrixFilter
8
+ from .models import MatrixFilter, IDENTIFICATION_MODE_POLYTOMOUS
9
9
  from .matrix_filters import MATRIX_FILTER_TYPES
10
10
 
11
11
  from .forms import is_active_field
@@ -63,6 +63,22 @@ class MatrixFilterManagementForm(LocalizeableForm):
63
63
  raise forms.ValidationError(_('A matrix filter with this name already exists.'))
64
64
 
65
65
  return name
66
+
67
+
68
+ def clean(self):
69
+ cleaned_data = super().clean()
70
+
71
+ identification_mode = self.meta_node.identification_mode
72
+
73
+ if identification_mode == IDENTIFICATION_MODE_POLYTOMOUS and not self.matrix_filter:
74
+
75
+ filter_exists = MatrixFilter.objects.filter(meta_node=self.meta_node).exists()
76
+
77
+ if filter_exists:
78
+ raise forms.ValidationError(_('In polytomous identification mode, only one matrix filter is allowed.'))
79
+
80
+
81
+ return cleaned_data
66
82
 
67
83
 
68
84
  class MatrixFilterManagementFormWithUnit(MatrixFilterManagementForm):
@@ -1130,6 +1130,7 @@ class CrosslinkManager:
1130
1130
 
1131
1131
  is_circular = False
1132
1132
 
1133
+ # you may not link a node to one of its parents
1133
1134
  if crosslink[0].startswith(crosslink[1]):
1134
1135
  is_circular = True
1135
1136
 
@@ -1156,17 +1157,17 @@ class CrosslinkManager:
1156
1157
 
1157
1158
  if is_circular == False:
1158
1159
 
1159
- found_connection = True
1160
-
1161
1160
  for crosslink in crosslinks:
1162
1161
 
1163
1162
  # start a new chain
1164
1163
  chain = [crosslink]
1164
+ found_connection = True
1165
1165
 
1166
1166
  while found_connection == True and is_circular == False:
1167
1167
 
1168
1168
  for crosslink_2 in crosslinks:
1169
1169
 
1170
+ # get the last nuid in the chain, which is a list of 2-tuples
1170
1171
  chain_end = chain[-1][1]
1171
1172
 
1172
1173
  found_connection = False
@@ -1212,6 +1213,8 @@ class NatureGuideCrosslinks(models.Model):
1212
1213
 
1213
1214
  all_crosslinks.append(crosslink_tuple)
1214
1215
 
1216
+ print('Checking crosslinks for circularity:')
1217
+ print(all_crosslinks)
1215
1218
  crosslink_manager = CrosslinkManager()
1216
1219
  is_circular = crosslink_manager.check_circularity(all_crosslinks)
1217
1220
 
@@ -10,15 +10,22 @@
10
10
  {% endblock %}
11
11
 
12
12
  {% block body %}
13
- <p>
14
- <div>
15
- {% render_bootstrap_form form %}
16
- </div>
17
- </p>
18
- {% if success is True %}
19
- <div class="alert alert-success">
20
- {% trans 'Successfully saved matrix filter.' %}
13
+
14
+ {% if adding_allowed == False and not matrix_filter %}
15
+ <div class="alert alert-warning">
16
+ {% trans "In dichotomous or polytomous identification mode, only one matrix filter is allowed." %}
21
17
  </div>
18
+ {% else %}
19
+ <p>
20
+ <div>
21
+ {% render_bootstrap_form form %}
22
+ </div>
23
+ </p>
24
+ {% if success is True %}
25
+ <div class="alert alert-success">
26
+ {% trans 'Successfully saved matrix filter.' %}
27
+ </div>
28
+ {% endif %}
22
29
  {% endif %}
23
30
  {% endblock %}
24
31
 
@@ -819,6 +819,14 @@ class ManageMatrixFilter(FormLanguageMixin, MetaAppMixin, FormView):
819
819
  context['meta_node'] = self.meta_node
820
820
  context['filter_type'] = self.filter_type
821
821
  context['matrix_filter'] = self.matrix_filter
822
+
823
+ adding_allowed = True
824
+ if self.meta_node.identification_mode == IDENTIFICATION_MODE_POLYTOMOUS:
825
+ existing_filters_count = MatrixFilter.objects.filter(meta_node=self.meta_node).count()
826
+ if existing_filters_count >= 1:
827
+ adding_allowed = False
828
+
829
+ context['adding_allowed'] = adding_allowed
822
830
 
823
831
  # fallback
824
832
  verbose_filter_name = self.filter_type
@@ -0,0 +1,37 @@
1
+ {% extends "localcosmos_server/modals/modal_form.html" %}
2
+ {% load i18n %}
3
+
4
+ {% block title %}
5
+ {% trans 'Delete All Manually Added Images' %}
6
+ {% endblock %}
7
+
8
+
9
+ {% block action %}
10
+ {% url 'delete_all_manually_added_taxon_profile_images' meta_app.id taxon_profiles.id %}
11
+ {% endblock %}
12
+
13
+ {% block body %}
14
+ <p>
15
+ {% if success %}
16
+ <div class="alert alert-success">
17
+ {% trans 'All manually added images have been successfully deleted.' %}
18
+ </div>
19
+ {% else %}
20
+ <div class="alert alert-danger">
21
+ {% trans 'Are you sure you want to delete all manually added images across all taxon profiles? This action cannot be undone.' %}
22
+ </div>
23
+ {% endif %}
24
+ </p>
25
+ {% endblock %}
26
+
27
+ {% block footer %}
28
+ {% if success %}
29
+ {% include 'localcosmos_server/modals/footers/close.html' %}
30
+ {% else %}
31
+ {% include 'localcosmos_server/modals/footers/delete.html' %}
32
+ {% endif %}
33
+ {% endblock %}
34
+
35
+ {% block script %}
36
+
37
+ {% endblock %}
@@ -63,7 +63,7 @@
63
63
  {% endif %}
64
64
  {% endfor %}
65
65
  {% if form.has_categories %}
66
- </div>
66
+ <!--</div>-->
67
67
  {% endif %}
68
68
  </div>
69
69
 
@@ -168,6 +168,22 @@
168
168
  </div>
169
169
  </div>
170
170
  </div>
171
+
172
+ <div class="mt-5">
173
+ <h3 class="text-danger">
174
+ {% trans 'Danger Zone' %}
175
+ </h3>
176
+ </div>
177
+ <div class="card mt-3">
178
+ <div class="card-body">
179
+ <div>
180
+ <button type="button" class="btn btn-outline-danger xhr" ajax-target="ModalContent" data-url="{% url 'delete_all_manually_added_taxon_profile_images' meta_app.id generic_content.id %}">{% trans 'Delete all Taxon Profile images that have been added manually' %}</button>
181
+ </div>
182
+ <div class="mt-3">
183
+ {% trans 'This will delete all manually added images across all taxon profiles.' %}
184
+ </div>
185
+ </div>
186
+ </div>
171
187
  {% endblock %}
172
188
 
173
189
  {% block extra_script %}
@@ -5,7 +5,7 @@ from django.urls import reverse
5
5
 
6
6
  from app_kit.tests.common import test_settings
7
7
 
8
- from app_kit.models import MetaAppGenericContent, ContentImage
8
+ from app_kit.models import MetaAppGenericContent, ContentImage, MetaApp
9
9
 
10
10
  from app_kit.tests.mixins import (WithMetaApp, WithTenantClient, WithUser, WithLoggedInUser, WithAjaxAdminOnly,
11
11
  WithAdminOnly, ViewTestMixin, WithImageStore, WithMedia, WithFormTest)
@@ -20,7 +20,8 @@ from app_kit.features.taxon_profiles.views import (ManageTaxonProfiles, ManageTa
20
20
  DeleteTaxonProfilesNavigationEntry, GetTaxonProfilesNavigation, ManageNavigationImage,
21
21
  DeleteNavigationImage, DeleteTaxonProfilesNavigationEntryTaxon, DeleteTaxonTextTypeCategory,
22
22
  ChangeNavigationEntryPublicationStatus, ManageTaxonTextTypeCategory, ManageTaxonTextSet,
23
- DeleteTaxonTextSet, GetTaxonTextsManagement, SetTaxonTextSetForTaxonProfile)
23
+ DeleteTaxonTextSet, GetTaxonTextsManagement, SetTaxonTextSetForTaxonProfile,
24
+ DeleteAllManuallyAddedTaxonProfileImages)
24
25
 
25
26
  from app_kit.features.taxon_profiles.models import (TaxonProfiles, TaxonProfile, TaxonTextType,
26
27
  TaxonText, TaxonProfilesNavigation, TaxonProfilesNavigationEntry,
@@ -2590,4 +2591,100 @@ class TestSetTaxonTextSetForTaxonProfile(WithTaxonProfile, WithTaxonProfiles, Vi
2590
2591
 
2591
2592
  self.taxon_profile.refresh_from_db()
2592
2593
 
2593
- self.assertEqual(self.taxon_profile.taxon_text_set, text_set)
2594
+ self.assertEqual(self.taxon_profile.taxon_text_set, text_set)
2595
+
2596
+
2597
+ class TestDeleteAllManuallyAddedTaxonProfileImages(WithTaxonProfile, WithTaxonProfiles, ViewTestMixin,
2598
+ WithImageStore, WithMedia, WithAjaxAdminOnly, WithUser, WithLoggedInUser, WithMetaApp, WithTenantClient, TenantTestCase):
2599
+
2600
+ url_name = 'delete_all_manually_added_taxon_profile_images'
2601
+ view_class = DeleteAllManuallyAddedTaxonProfileImages
2602
+
2603
+ def get_url_kwargs(self):
2604
+ url_kwargs = {
2605
+ 'meta_app_id': self.meta_app.id,
2606
+ 'taxon_profiles_id': self.generic_content.id,
2607
+ }
2608
+ return url_kwargs
2609
+
2610
+ def create_content_images(self):
2611
+
2612
+ # taxon image
2613
+ self.taxon_image_store = self.create_image_store()
2614
+
2615
+ # add image to nature guide meta node
2616
+ self.meta_node_image = self.create_content_image(self.meta_app, self.user)
2617
+
2618
+ # add image to taxon profile
2619
+ self.taxon_profile_image = self.create_content_image(self.taxon_profile, self.user)
2620
+
2621
+
2622
+
2623
+ @test_settings
2624
+ def test_set_taxon_profiles(self):
2625
+
2626
+ view = self.get_view()
2627
+ view.meta_app = self.meta_app
2628
+ view.set_taxon_profiles(**view.kwargs)
2629
+
2630
+ self.assertEqual(view.taxon_profiles, self.generic_content)
2631
+
2632
+ @test_settings
2633
+ def test_get_context_data(self):
2634
+
2635
+ view = self.get_view()
2636
+ view.meta_app = self.meta_app
2637
+ view.set_taxon_profiles(**view.kwargs)
2638
+
2639
+ context = view.get_context_data(**view.kwargs)
2640
+
2641
+ self.assertEqual(context['taxon_profiles'], self.generic_content)
2642
+ self.assertFalse(context['success'])
2643
+
2644
+ @test_settings
2645
+ def test_post(self):
2646
+
2647
+ view = self.get_view()
2648
+ view.meta_app = self.meta_app
2649
+ view.set_taxon_profiles(**view.kwargs)
2650
+
2651
+ self.create_content_images()
2652
+
2653
+ taxon_profile_ctype = ContentType.objects.get_for_model(TaxonProfile)
2654
+ meta_app_ctype = ContentType.objects.get_for_model(MetaApp)
2655
+
2656
+ # verify image exists
2657
+ images_qry = ContentImage.objects.filter(
2658
+ content_type=taxon_profile_ctype,
2659
+ object_id=self.taxon_profile.id,
2660
+ )
2661
+ self.assertTrue(images_qry.exists())
2662
+
2663
+ meta_app_images_qry = ContentImage.objects.filter(
2664
+ content_type=meta_app_ctype,
2665
+ object_id=self.meta_app.id,
2666
+ )
2667
+
2668
+ self.assertTrue(meta_app_images_qry.exists())
2669
+
2670
+ # perform post to delete images
2671
+ response = view.post(view.request, **view.kwargs)
2672
+
2673
+ self.assertEqual(response.status_code, 200)
2674
+ self.assertTrue(response.context_data['success'])
2675
+
2676
+ # verify images deleted
2677
+ images_qry = ContentImage.objects.filter(
2678
+ content_type=taxon_profile_ctype,
2679
+ object_id=self.taxon_profile.id,
2680
+ )
2681
+ self.assertFalse(images_qry.exists())
2682
+
2683
+ # verify meta app image still exists
2684
+ meta_app_images_qry = ContentImage.objects.filter(
2685
+ content_type=meta_app_ctype,
2686
+ object_id=self.meta_app.id,
2687
+ )
2688
+ self.assertTrue(meta_app_images_qry.exists())
2689
+
2690
+
@@ -119,4 +119,7 @@ urlpatterns = [
119
119
  # move images
120
120
  path('move-taxon-profile-image-to-section/<int:meta_app_id>/<int:taxon_profile_id>/<int:content_image_id>/',
121
121
  views.MoveImageToSection.as_view(), name='move_taxon_profile_image_to_section'),
122
+ # delete all manually added images
123
+ path('delete-all-manually-added-taxon-profile-images/<int:meta_app_id>/<int:taxon_profiles_id>/',
124
+ views.DeleteAllManuallyAddedTaxonProfileImages.as_view(), name='delete_all_manually_added_taxon_profile_images'),
122
125
  ]
@@ -1580,4 +1580,41 @@ class MoveImageToSection(MetaAppMixin, FormView):
1580
1580
  context['success'] = True
1581
1581
 
1582
1582
  return self.render_to_response(context)
1583
-
1583
+
1584
+
1585
+ class DeleteAllManuallyAddedTaxonProfileImages(MetaAppMixin, TemplateView):
1586
+
1587
+ template_name = 'taxon_profiles/ajax/delete_all_manually_added_images.html'
1588
+
1589
+ @method_decorator(ajax_required)
1590
+ def dispatch(self, request, *args, **kwargs):
1591
+ self.set_taxon_profiles(**kwargs)
1592
+ return super().dispatch(request, *args, **kwargs)
1593
+
1594
+ def set_taxon_profiles(self, **kwargs):
1595
+ self.taxon_profiles = TaxonProfiles.objects.get(pk=kwargs['taxon_profiles_id'])
1596
+
1597
+ def get_context_data(self, **kwargs):
1598
+ context = super().get_context_data(**kwargs)
1599
+ context['taxon_profiles'] = self.taxon_profiles
1600
+ context['success'] = False
1601
+ return context
1602
+
1603
+ def post(self, request, *args, **kwargs):
1604
+
1605
+ taxon_profile_ctype = ContentType.objects.get_for_model(TaxonProfile)
1606
+ all_taxon_profiles = TaxonProfile.objects.filter(taxon_profiles=self.taxon_profiles)
1607
+
1608
+ images_to_delete = ContentImage.objects.filter(
1609
+ content_type=taxon_profile_ctype,
1610
+ object_id__in=all_taxon_profiles.values_list('id', flat=True)
1611
+ )
1612
+
1613
+ deleted_count = images_to_delete.count()
1614
+ images_to_delete.delete()
1615
+
1616
+ context = self.get_context_data(**self.kwargs)
1617
+ context['deleted_count'] = deleted_count
1618
+ context['success'] = True
1619
+
1620
+ return self.render_to_response(context)
@@ -260,6 +260,7 @@ class TaxonProfilesZipImporter(GenericContentZipImporter):
260
260
 
261
261
 
262
262
  def validate_external_media(self, url, col_letter, row_index):
263
+
263
264
  validator = URLValidator()
264
265
 
265
266
  is_valid = True
@@ -566,6 +567,9 @@ class TaxonProfilesZipImporter(GenericContentZipImporter):
566
567
  elif column_type == ColumnType.EXTERNAL_MEDIA.value:
567
568
 
568
569
  external_media_url = cell_value
570
+
571
+ if not external_media_url:
572
+ continue
569
573
 
570
574
  external_media_data = self.get_external_media_data_from_external_media_sheet(external_media_url)
571
575
 
app_kit/forms.py CHANGED
@@ -18,6 +18,8 @@ from app_kit.appbuilder.AppBuilderBase import AppBuilderBase
18
18
  from taxonomy.models import MetaVernacularNames
19
19
  from taxonomy.lazy import LazyTaxon
20
20
 
21
+ from content_licencing.mixins import LicencingFormMixin
22
+
21
23
  import base64, math, uuid
22
24
 
23
25
  from .definitions import TEXT_LENGTH_RESTRICTIONS
@@ -130,7 +132,13 @@ class AddLanguageForm(forms.Form):
130
132
  '''
131
133
  from localcosmos_server.forms import (ManageContentImageForm, ManageContentImageWithTextForm,
132
134
  ManageLocalizedContentImageForm, OptionalContentImageForm)
135
+
136
+ class ManageContentLicenceForm(LicencingFormMixin):
137
+ content_field = None
133
138
 
139
+ def __init__(self, content_field, *args, **kwargs):
140
+ self.content_field = content_field
141
+ super().__init__(*args, **kwargs)
134
142
 
135
143
  class GenericContentOptionsForm(forms.Form):
136
144
 
Binary file