class ModsEditForm(XmlObjectForm): """:class:`~eulxml.forms.XmlObjectForm` for editing :class:`~keep.video.models.VideoMods`. """ # ARK value is set in form instance data by VideoEditForm init # read-only text input to display ARK (not editable) identifier = forms.CharField(label="Identifier", required=False, widget=ReadonlyTextInput) resource_type = forms.CharField(required=False, widget=ReadonlyTextInput) origin_info = SubformField(formclass=OriginInfoForm) general_note = SubformField(formclass=SimpleNoteForm) names = SubformField(formclass=NameForm) class Meta: model = VideoMods fields = ( 'identifier', 'dm1_id', 'dm1_other_id', 'title', 'origin_info', 'general_note', 'resource_type', ) widgets = { 'title': forms.Textarea(attrs={'class': 'form-control'}), 'identifier': ReadonlyTextInput, 'dm1_id': ReadonlyTextInput, 'dm1_other_id': ReadonlyTextInput, }
class EnvironmentForm(XmlObjectForm): software = SubformField(formclass=SoftwareEnvironmentForm) hardware = SubformField(formclass=HardwareEnvironmentForm) class Meta: model = PremisEnvironment fields = ('software', 'hardware')
class ModsEditForm(XmlObjectForm): """:class:`~eulxml.forms.XmlObjectForm` for editing :class:`~keep.common.fedora.LocalMODS`. """ # ARK value is set in form instance data by AudioObjectEditForm init # read-only text input to display ARK (not editable) identifier = forms.CharField(label="Identifier", required=False, widget=ReadonlyTextInput) abstract = SubformField(formclass=AbstractForm) coveringdate_start = W3CDateField( label='Covering Dates', help_text= 'start and end dates for disk content in YYYY, YYYY-MM, or YYYY-MM-DD', required=False) coveringdate_end = W3CDateField(label='', required=False) class Meta: model = DiskImageMods fields = ('title', 'identifier', 'resource_type', 'genre', 'coveringdate_start', 'coveringdate_end', 'abstract') widgets = { 'title': forms.TextInput(attrs={'class': 'long'}), 'identifier': ReadonlyTextInput, 'resource_type': ReadonlyTextInput, 'genre': ReadonlyTextInput, }
class SeriesPartForm(XmlObjectForm): ''' Custom :class:`~eulxml.forms.XmlObjectForm` to edit Series information. ''' title = forms.CharField( label='Series Title', required=False, widget=forms.TextInput(attrs={'class': 'long series1_storedtitle'})) uri = forms.CharField( label='URI', required=False, widget=forms.TextInput(attrs={'class': 'long series1_storeduri'})) base_ark = forms.CharField( label='Base Ark', required=False, widget=forms.TextInput(attrs={'class': 'long series1_storedark'})) full_id = forms.CharField( label='Full ID', required=False, widget=forms.TextInput(attrs={'class': 'long series1_storedfullid'})) short_id = forms.CharField( label='Short ID', required=False, widget=forms.TextInput(attrs={'class': 'long series1_storedshortid'})) series = SubformField(formclass=Series2PartForm) class Meta: fields = ['series', 'title', 'uri', 'base_ark', 'full_id', 'short_id'] model = Series1
class NameForm(XmlObjectForm): '''Custom :class:`~eulxml.forms.XmlObjectForm` to edit MODS :class:`~eulxml.xmlmap.mods.Name` information. * use custom :class:`~eulxml.xmlmap.mods.NamePart` and :class:`~eulxml.xmlmap.mods.Role` forms (:class:`NamePartForm`, :class:`RoleForm`) * customize id field label & help text * suppress displayForm and affiliation fields ''' id = forms.CharField(required=False, label='Identifier', widget=forms.TextInput(attrs={'class': 'form-control'}), help_text="Optional; supply for NAF names.") name_parts = SubformField(formclass=NamePartForm) roles = SubformField(formclass=RoleForm) class Meta: model = mods.Name exclude = ['display_form', 'affiliation']
class FileTechEditForm(XmlObjectForm): """:class:`~eulxml.forms.XmlObjectForm` to edit :class:`~keep.common.models.Rights` metadata. """ file = SubformField(formclass=FileTechPartForm, can_delete=False) class Meta: model = FileMasterTech fields = ['file']
class OriginInfoForm(XmlObjectForm): """Custom :class:`~eulxml.forms.XmlObjectForm` to edit MODS :class:`~eulxml.xmlmap.mods.OriginInfo`. Currently only consists of simple date entry for date created and issued using :class:`SimpleDateForm`. """ form_label = 'Origin Info' #Create the subform fields from fields (xmlmap) in eulxml. created = SubformField(formclass=xmlobjectform_factory( mods.DateCreated, form=SimpleDateForm, max_num=1, can_delete=False)) issued = SubformField(formclass=xmlobjectform_factory(mods.DateIssued, form=SimpleDateForm, max_num=1, can_delete=False), label='Date Issued') class Meta: model = mods.OriginInfo fields = ['created', 'issued']
class ArrangementModsForm(XmlObjectForm): """ :class:`~eulxml.forms.XmlObjectForm` to edit :class:`~keep.arrangment.models.ArrangementMods` metadata. """ series = SubformField(formclass=SeriesPartForm) class Meta: model = ArrangementMods fields = ['series']
class JournalEditForm(BaseXmlObjectForm): form_label = 'Publication Information' title = forms.CharField(label='Journal Title', widget=forms.TextInput(attrs={'class': 'text'})) issn = forms.CharField(label='ISSN', required=False) volume = SubformField(label='Volume #', formclass=PartDetailNumberEditForm, widget=forms.TextInput(attrs={'class': 'text'})) number = SubformField(label='Issue #', formclass=PartDetailNumberEditForm, widget=forms.TextInput(attrs={'class': 'text'})) pages = SubformField(formclass=PartExtentEditForm, label='Page Range') class Meta: model = JournalMods fields = ['title', 'issn', 'publisher', 'volume', 'number', 'pages'] widgets = { 'publisher': forms.TextInput(attrs={'class': 'text'}), 'issn': forms.HiddenInput, # populated by autocomplete }
class MyForm(TestForm): child = SubformField(formclass=MySubForm, label="TEST ME") class Meta: model = MyTestObj
class MySubForm(XmlObjectForm): parts = SubformField(formclass=MySubFormset) class Meta: model = MyTestSubObj
class MyForm(TestForm): children = SubformField(formclass=MySubForm, label="TEST ME")
class MyForm(TestForm): child = SubformField(formclass=MySubForm, label=subform_label) class Meta: model = TestObject fields = ['id', 'int', 'child']
class OrderedTestForm(XmlObjectForm): children = SubformField(formclass=MySubFormset) class Meta: model = TestObject
class PremisEditForm(XmlObjectForm): # NOTE: extending xmlobjet form to make use of existing schema validation # logic & integration to form validation. # However, this form does not use any xmlobject form fields. application = DynamicChoiceField(label='Creating Application', choices=creating_applications) date = forms.DateField( label='Imaging Date', input_formats=['%Y-%m-%d', '%Y/%m/%d'], help_text='Date created in YYYY-MM-DD or YYYY/MM/DD format') object = SubformField(PremisObject) class Meta: model = DiskImagePremis fields = ('object', ) def __init__(self, data=None, instance=None, prefix=None, initial={}, **kwargs): if instance is not None: # set initial form data based on the xml # NOTE: migrated objects have two sets of characteristics, # and the creating application to be displayed is the second one. # Always display and update the last of any creating applications if instance.object and instance.object.characteristics and \ instance.object.characteristics[-1].creating_application: creating_app = instance.object.characteristics[ -1].creating_application initial['date'] = creating_app.date try: app = Application.objects.get(name=creating_app.name, version=creating_app.version) initial['application'] = app.id except ObjectDoesNotExist: pass super(PremisEditForm, self).__init__(data=data, instance=instance, prefix=prefix, initial=initial, **kwargs) def update_instance(self): super(PremisEditForm, self).update_instance() data = self.cleaned_data # update premis fields based on form data self.instance.object.create_creating_application() app = Application.objects.get(id=data['application']) # Update the last of any creating applications in the metadata self.instance.object.characteristics[ -1].creating_application.name = app.name self.instance.object.characteristics[ -1].creating_application.version = app.version self.instance.object.characteristics[ -1].creating_application.date = data['date'] return self.instance
class CollectionForm(XmlObjectForm): '''Custom :class:`~eulxml.forms.XmlObjectForm` to edit descriptive metadata on a :class:`~keep.collection.models.CollectionObject`. Takes a :class:`~keep.collection.models.CollectionObject` as form instance. This stands in contrast to a regular :class:`~eulxml.forms.XmlObjectForm`, which would take an :class:`~euxml.xmlmap.XmlObject`. This form edits a whole :class:`~keep.collection.models.CollectionObject`, although most of the editing is on the MODS datastream (which is an :class:`~eulxml.xmlmap.XmlObject`). The most expedient way to make a :class:`~keep.collection.models.CollectionObject` editable was to make a customized :class:`~eulxml.forms.XmlObjectForm` that mostly deals with the MODS datastream. ''' error_css_class = 'has-error' # TODO: would be nice to have an ObjectChoiceField analogous to django's ModelChoiceField collection = DynamicChoiceField(label="Archive", choices=archive_choices, required=True, help_text="Owning repository for this collection of materials.") # using URI because it will be used to set a relation in RELS-EXT source_id = forms.IntegerField(label="Source Identifier", help_text="Source archival collection number (enter 100 for MSS100 or Series 100)", widget=forms.TextInput(attrs={'class': 'form-control'})) title = forms.CharField(help_text="Title of the archival collection", widget=forms.TextInput(attrs={'class': 'form-control'})) # NOTE: handling date range with custom input forms and logic on update_instance # this could possibly be handled by a custom XmlObjectForm for originInfo date_created = forms.CharField(help_text="Date created, or start date for a date range.", required=False) date_end = forms.CharField(help_text="End date for a date range. Leave blank if not a range.", required=False) name = SubformField(formclass=NameForm) restrictions_on_access = SubformField(formclass=AccessConditionForm) use_and_reproduction = SubformField(formclass=AccessConditionForm) comment = forms.CharField(label="Comment", required=False, help_text="Optional description of changes made.", widget=forms.TextInput(attrs={'class': 'form-control'})) class Meta: model = MODS fields = ( 'collection', 'source_id', 'title', 'resource_type', 'name', 'restrictions_on_access', 'use_and_reproduction', ) widgets = { 'resource_type': forms.Select(attrs={'class': 'form-control'}), } def __init__(self, data=None, instance=None, **kwargs): # overriding init to accept a CollectionObject instead of CollectionMods # - set initial data for extra fields (collection & dates) from instance # - pass mods xmlobject to parent XmlObjectForm if instance is not None: # store the digital object, store mods to pass on to parent init self.object_instance = instance mods_instance = instance.mods.content # populate fields not auto-generated & handled by XmlObjectForm initial = {} if mods_instance.origin_info and \ mods_instance.origin_info.created: initial['date_created'] = mods_instance.origin_info.created[0].date if len(mods_instance.origin_info.created) > 1: initial['date_end'] = mods_instance.origin_info.created[1].date if self.object_instance.collection: initial['collection'] = self.object_instance.collection.pid if 'initial' not in kwargs: kwargs['initial'] = {} kwargs['initial'].update(initial) else: mods_instance = None super(CollectionForm, self).__init__(data=data, instance=mods_instance, **kwargs) def clean(self): """Perform cross-field validation/cleaning on the form. Currently, verify that collection and source_id are unique together. """ cleaned_data = super(CollectionForm, self).clean() if cleaned_data.get('collection', '') and \ (cleaned_data.get('source_id', '') or cleaned_data.get('source_id', '') == 0) and \ self._duplicate_exists(cleaned_data): msg = "A collection already exists with this Archive and Source Id." self._errors['collection'] = self.error_class([msg]) self._errors['source_id'] = self.error_class([msg]) del cleaned_data['collection'] del cleaned_data['source_id'] return cleaned_data def _duplicate_exists(self, cleaned_data): """Determine if saving this form would create a duplicate collection. Specifically, verify that there is no other collection with the same collection (archive) and source_id present in solr. """ collection = cleaned_data.get('collection') source_id = cleaned_data.get('source_id') solr = solr_interface() query = solr.query( content_model=CollectionObject.COLLECTION_CONTENT_MODEL, source_id=source_id, archive_id=collection) response = query.execute() # if there are no matches then this is definitely not a if response.result.numFound == 0: return False if response.result.numFound > 1: # if there's already more than one match then this is definitely # a duplicate return True # otherwise there's exactly one. if it's this object then this *is* # the collection with that archive/id. return (response[0]['pid'] != self.object_instance.pid) def update_instance(self): # override default update to handle extra fields (collection & dates) # NOTE: collection membership can only be set when a CollectionObject # was passed in as form instance super(CollectionForm, self).update_instance() # cleaned data only available when the form is valid, # but xmlobjectform is_valid calls update_instance if hasattr(self, 'cleaned_data'): # set date created - could be a single date or a date range # remove existing dates and re-add self.instance.create_origin_info() for i in range(len(self.instance.origin_info.created)): self.instance.origin_info.created.pop() self.instance.origin_info.created.append(mods.DateCreated( date=self.cleaned_data['date_created'], key_date=True, )) # if there is a date end, store it and set end & start attributes if 'date_end' in self.cleaned_data and self.cleaned_data['date_end']: self.instance.create_origin_info() self.instance.origin_info.created.append(mods.DateCreated( date=self.cleaned_data['date_end'], point='end', )) self.instance.origin_info.created[0].point = 'start' # set relation to archive object when an instance was passed in if hasattr(self, 'object_instance'): self.object_instance.collection = self.object_instance.get_object(self.cleaned_data['collection']) # must return mods portion because XmlObjectForm depends on it for validation return self.instance
class ArticleModsEditForm(BaseXmlObjectForm): '''Form to edit the MODS descriptive metadata for an :class:`~openemory.publication.models.Article`. Takes optional :param: make_optional that makes all fields but Article Title optional Takes optional :param: is_admin Takes optional :param: nlm ''' title_info = SubformField(formclass=ArticleModsTitleEditForm) authors = SubformField(formclass=AuthorNameForm) funders = SubformField(formclass=FundingGroupEditForm) journal = SubformField(formclass=JournalEditForm) final_version = SubformField(formclass=FinalVersionForm) abstract = SubformField(formclass=AbstractEditForm) supplemental_materials = SubformField( formclass=SupplementalMaterialEditForm) copyright = SubformField(formclass=CopyrightEditForm) admin_note = SubformField(formclass=AdminNoteEditForm) keywords = SubformField(formclass=KeywordEditForm) author_notes = SubformField(formclass=AuthorNotesEditForm) #locations = SubformField(formclass=OtherURLSForm, # label=OtherURLSForm.form_label) language_code = DynamicChoiceField(language_choices, label='Language', help_text='Language of the article') subjects = SubformField(formclass=SubjectForm) # admin-only fields reviewed = forms.BooleanField( help_text='Select to indicate this article has been ' + 'reviewed; this will store a review event and remove ' + 'the article from the review queue.', required=False) # does not have to be checked withdraw = forms.BooleanField( help_text='Remove this article from the ' + 'public-facing parts of this site. It will still be visible to ' + 'admins and article authors.', required=False) withdraw_reason = forms.CharField( required=False, label='Reason', help_text='Reason for withdrawing this article') reinstate = forms.BooleanField(help_text='Return this withdrawn article ' + 'to the public-facing parts of this site.', required=False) reinstate_reason = forms.CharField( required=False, label='Reason', help_text='Reason for reinstating this article') _embargo_choices = [('', 'no embargo'), ('6 months', '6 months'), ('1 year', '1 year'), ('18 months', '18 months'), ('2 years', '2 years'), ('3 years', '3 years')] embargo_duration = forms.ChoiceField( _embargo_choices, help_text= 'Restrict access to the PDF of your article for the selected time ' + 'after publication.', required=False) #author_agreement = forms.FileField(required=False, # help_text="Upload a copy of the " + # "article's author agreement.", # widget=forms.FileInput(attrs={'class': 'text'}), # validators=[FileTypeValidator(types=['application/pdf'], # message=PDF_ERR_MSG)]) publication_date = W3CDateField( widget=LocalW3CDateWidget, error_messages={ 'invalid': u'Enter at least year (YYYY); ' + u'enter two-digit month and day if known.', 'required': 'Publication year is required.' }) rights_research_date = forms.DateField(widget=DateInput(format='%Y-%m-%d', attrs={ 'class': 'text', 'style': 'width:150px' }), help_text='Format: yyyy-mm-dd', required=False, label='Rights Research Date') featured = forms.BooleanField( label='Featured', required=False, help_text='''Select to indicate this article has been featured; this will put this article in the list of possible articles that will appear on the home page.''') license = DynamicChoiceField(license_choices, label='Creative Commons License', required=False, help_text='Select appropriate license') class Meta: model = ArticleMods fields = [ 'title_info', 'authors', 'version', 'publication_date', 'subjects', 'funders', 'journal', 'final_version', 'abstract', 'keywords', 'author_notes', 'language_code', 'copyright', 'admin_note', 'rights_research_date', 'supplemental_materials' ] ''' :param: url: url of the license being referenced Looks up values for #permits terms on given license, retrieves the values from http://creativecommons.org/ns and constructs a description of the license. ''' def _license_desc(self, url): permits_uri = URIRef("http://creativecommons.org/ns#permits") requires_uri = URIRef("http://creativecommons.org/ns#requires") prohibits_uri = URIRef("http://creativecommons.org/ns#prohibits") comment_uri = URIRef(u'http://www.w3.org/2000/01/rdf-schema#comment') ns_url = 'http://creativecommons.org/ns' license_graph = Graph() license_graph.parse(url) ns_graph = Graph() ns_graph.parse(ns_url) lines = [] title = License.objects.get(url=url).title desc = 'This is an Open Access article distributed under the terms of the Creative Commons %s License \ ( %s),' % (title, url) # get permits terms for t in license_graph.subject_objects(permits_uri): lines.append( ns_graph.value(subject=URIRef(t[1]), predicate=comment_uri, object=None)) if lines: desc += ' which permits %s, provided the original work is properly cited.' % ( ', '.join(lines)) # get requires terms lines = [] for t in license_graph.subject_objects(requires_uri): lines.append( ns_graph.value(subject=URIRef(t[1]), predicate=comment_uri, object=None)) if lines: desc += ' This license requires %s.' % (', '.join(lines)) # get prohibits terms lines = [] for t in license_graph.subject_objects(prohibits_uri): lines.append( ns_graph.value(subject=URIRef(t[1]), predicate=comment_uri, object=None)) if lines: desc += ' This license prohibits %s.' % (', '.join(lines)) #remove tabs, newlines and extra spaces desc = re.sub('\t+|\n+', ' ', desc) desc = re.sub(' +', ' ', desc) return desc def __init__(self, *args, **kwargs): #When set this marks the all fields EXCEPT for Title as optional make_optional = kwargs.pop('make_optional', False) is_admin = kwargs.pop('is_admin', False) is_nlm = kwargs.pop('is_nlm', False) self.pid = kwargs.pop('pid') ''':param: make_optional: when set this makes all the fields EXCEPT Article Title optional Currently, only used in the case where the "Save" (vs Publish) button is used. :param: pid: pid of the :class:`~openemory.publication.models.Article` being edited. Will be None if user does not have the review perm or the article is not published. ''' super(ArticleModsEditForm, self).__init__(*args, **kwargs) # set default language to english lang_code = 'language_code' if lang_code not in self.initial or not self.initial[lang_code]: self.initial[lang_code] = 'eng' if make_optional: for author_fs in self.formsets['authors']: author_fs.fields['family_name'].required = False author_fs.fields['given_name'].required = False self.fields['version'].required = False self.fields['publication_date'].required = False self.fields['language_code'].required = False self.subforms['journal'].fields['title'].required = False self.subforms['journal'].fields['publisher'].required = False if is_admin and not is_nlm: self.fields['rights_research_date'].required = True embargo = 'embargo_duration' if embargo not in self.initial or not self.initial[embargo]: # if embargo is set in metadata, use that as initial value if self.instance.embargo: self.initial[embargo] = self.instance.embargo # otherwise, fall through to default choice (no embargo) license = 'license' if self.instance.license: self.initial[license] = self.instance.license.link def clean(self): cleaned_data = super(ArticleModsEditForm, self).clean() withdraw = self.cleaned_data.get('withdraw', False) withdraw_reason = self.cleaned_data.get('withdraw_reason', '') if self.cleaned_data.get('withdraw', False) and not withdraw_reason: message = "Withdrawal reason is required." self._errors['withdraw_reason'] = self.error_class([message]) reinstate = self.cleaned_data.get('reinstate', False) reinstate_reason = self.cleaned_data.get('reinstate_reason', '') if self.cleaned_data.get('reinstate', False) and not reinstate_reason: message = "Reinstate reason is required." self._errors['reinstate_reason'] = self.error_class([message]) return cleaned_data def update_instance(self): # override default update to handle extra fields super(ArticleModsEditForm, self).update_instance() # cleaned data only available when the form is actually valid if hasattr(self, 'cleaned_data'): # set or clear language text value based on language code lang_code = self.cleaned_data.get('language_code', None) if lang_code is None: # if language code is blank, clear out language text self.instance.language = None else: # otherwise, set text value based on language code self.instance.language = language_codes()[lang_code] embargo = self.cleaned_data.get('embargo_duration', None) # if not set or no embargo selected, clear out any previous value if embargo is None or not embargo: del self.instance.embargo else: self.instance.embargo = embargo if self.pid: # only do this if pid is set which means that the user has the correct perms # set / remove featured article featured = self.cleaned_data.get('featured') try: featured_article = FeaturedArticle.objects.get( pid=self.pid) except FeaturedArticle.DoesNotExist: featured_article = FeaturedArticle(pid=self.pid) if featured: # box is checked on form featured_article.save() elif featured is not None and featured_article.id: #have to check it exists before you delete featured_article.delete() license_url = self.cleaned_data.get('license') if license_url: self.instance.create_license() self.instance.license.link = license_url self.instance.license.text = self._license_desc(license_url) else: self.instance.license = None # return object instance return self.instance
class PremisObject(XmlObjectForm): original_environment = SubformField(formclass=EnvironmentForm) class Meta: model = PremisObject fields = ('original_environment', )