Beispiel #1
0
def update_collection_by_deletion_of_referenced_form(collection,
                                                     referenced_form):
    """Update a collection based on the deletion of a form it references.

    This function is called in the :class:`FormsController` when a form is
    deleted.  It is called on each collection that references the deleted form
    and the changes to each of those collections are propagated through all of
    the collections that reference them, and so on.
    
    :param collection: a collection model object.
    :param referenced_form: a form model object.
    :returns: ``None``.

    """
    collection_dict = collection.get_full_dict()
    collection.contents = remove_references_to_this_form(
        collection.contents, referenced_form.id)
    collections_referenced = get_collections_referenced(collection.contents)
    collection.contents_unpacked = generate_contents_unpacked(
        collection.contents, collections_referenced)
    collection.html = h.get_HTML_from_contents(collection.contents_unpacked,
                                               collection.markup_language)
    collection.datetime_modified = datetime.datetime.utcnow()
    backup_collection(collection_dict)
    update_collections_that_reference_this_collection(
        collection,
        OldcollectionsController.query_builder,
        contents_changed=True)
    Session.add(collection)
    Session.commit()
Beispiel #2
0
def update_collection_by_deletion_of_referenced_form(collection, referenced_form):
    """Update a collection based on the deletion of a form it references.

    This function is called in the :class:`FormsController` when a form is
    deleted.  It is called on each collection that references the deleted form
    and the changes to each of those collections are propagated through all of
    the collections that reference them, and so on.
    
    :param collection: a collection model object.
    :param referenced_form: a form model object.
    :returns: ``None``.

    """
    collection_dict = collection.get_full_dict()
    collection.contents = remove_references_to_this_form(collection.contents, referenced_form.id)
    collections_referenced = get_collections_referenced(collection.contents)
    collection.contents_unpacked = generate_contents_unpacked(
                                collection.contents, collections_referenced)
    collection.html = h.get_HTML_from_contents(collection.contents_unpacked,
                                              collection.markup_language)
    collection.datetime_modified = datetime.datetime.utcnow()
    backup_collection(collection_dict)
    update_collections_that_reference_this_collection(
        collection, OldcollectionsController.query_builder, contents_changed=True)
    Session.add(collection)
    Session.commit()
Beispiel #3
0
def update_speaker(speaker, data):
    """Update a speaker.

    :param speaker: the speaker model to be updated.
    :param dict data: representation of the updated speaker.
    :returns: the updated speaker model or, if ``changed`` has not been set
        to ``True``, ``False``.

    """
    changed = False

    # Unicode Data
    changed = speaker.set_attr('first_name', h.normalize(data['first_name']), changed)
    changed = speaker.set_attr('last_name', h.normalize(data['last_name']), changed)
    changed = speaker.set_attr('dialect', h.normalize(data['dialect']), changed)
    changed = speaker.set_attr('page_content', h.normalize(data['page_content']), changed)
    changed = speaker.set_attr('markup_language', h.normalize(data['markup_language']), changed)
    changed = speaker.set_attr('html',
                        h.get_HTML_from_contents(speaker.page_content, speaker.markup_language),
                        changed)

    if changed:
        speaker.datetime_modified = datetime.datetime.utcnow()
        return speaker
    return changed
Beispiel #4
0
def create_new_user(data):
    """Create a new user.

    :param dict data: the data for the user to be created.
    :returns: an SQLAlchemy model object representing the user.

    """
    user = User()
    user.salt = h.generate_salt()
    user.password = unicode(h.encrypt_password(data['password'], str(user.salt)))
    user.username = h.normalize(data['username'])
    user.first_name = h.normalize(data['first_name'])
    user.last_name = h.normalize(data['last_name'])
    user.email = h.normalize(data['email'])
    user.affiliation = h.normalize(data['affiliation'])
    user.role = h.normalize(data['role'])
    user.markup_language = h.normalize(data['markup_language'])
    user.page_content = h.normalize(data['page_content'])
    user.html = h.get_HTML_from_contents(user.page_content, user.markup_language)

    # Many-to-One Data: input and output orthographies
    if data['input_orthography']:
        user.input_orthography= data['input_orthography']
    if data['output_orthography']:
        user.output_orthography = data['output_orthography']

    # OLD-generated Data
    user.datetime_modified = datetime.datetime.utcnow()

    # Create the user's directory
    h.create_user_directory(user)

    return user
Beispiel #5
0
def create_new_user(data):
    """Create a new user.

    :param dict data: the data for the user to be created.
    :returns: an SQLAlchemy model object representing the user.

    """
    user = User()
    user.salt = h.generate_salt()
    user.password = unicode(
        h.encrypt_password(data['password'], str(user.salt)))
    user.username = h.normalize(data['username'])
    user.first_name = h.normalize(data['first_name'])
    user.last_name = h.normalize(data['last_name'])
    user.email = h.normalize(data['email'])
    user.affiliation = h.normalize(data['affiliation'])
    user.role = h.normalize(data['role'])
    user.markup_language = h.normalize(data['markup_language'])
    user.page_content = h.normalize(data['page_content'])
    user.html = h.get_HTML_from_contents(user.page_content,
                                         user.markup_language)

    # Many-to-One Data: input and output orthographies
    if data['input_orthography']:
        user.input_orthography = data['input_orthography']
    if data['output_orthography']:
        user.output_orthography = data['output_orthography']

    # OLD-generated Data
    user.datetime_modified = datetime.datetime.utcnow()

    # Create the user's directory
    h.create_user_directory(user)

    return user
Beispiel #6
0
def create_new_collection(data, collections_referenced):
    """Create a new collection.

    :param dict data: the collection to be created.
    :param dict collections_referenced: the collection models recursively referenced in ``data['contents']``.
    :returns: an SQLAlchemy model object representing the collection.

    """
    collection = Collection()
    collection.UUID = unicode(uuid4())

    # User-inputted string data
    collection.title = h.normalize(data['title'])
    collection.type = h.normalize(data['type'])
    collection.url = h.normalize(data['url'])
    collection.description = h.normalize(data['description'])
    collection.markup_language = h.normalize(data['markup_language'])
    collection.contents = h.normalize(data['contents'])
    collection.contents_unpacked = h.normalize(data['contents_unpacked'])
    collection.html = h.get_HTML_from_contents(collection.contents_unpacked,
                                            collection.markup_language)

    # User-inputted date: date_elicited
    collection.date_elicited = data['date_elicited']

    # Many-to-One
    if data['elicitor']:
        collection.elicitor = data['elicitor']
    if data['speaker']:
        collection.speaker = data['speaker']
    if data['source']:
        collection.source = data['source']

    # Many-to-Many: tags, files & forms
    collection.tags = [t for t in data['tags'] if t]
    collection.files = [f for f in data['files'] if f]
    collection.forms = [f for f in data['forms'] if f]

    # Restrict the entire collection if it is associated to restricted forms or
    # files or if it references a restricted collection in its contents field.
    immediately_referenced_collections = get_collections_referenced_in_contents(
                                            collection, collections_referenced)
    tags = [f.tags for f in collection.files + collection.forms + immediately_referenced_collections]
    tags = [tag for tag_list in tags for tag in tag_list]
    restricted_tags = [tag for tag in tags if tag.name == u'restricted']
    if restricted_tags:
        restricted_tag = restricted_tags[0]
        if restricted_tag not in collection.tags:
            collection.tags.append(restricted_tag)

    # OLD-generated Data
    now = datetime.datetime.utcnow()
    collection.datetime_entered = now
    collection.datetime_modified = now
    collection.enterer = collection.modifier = session['user']

    return collection
Beispiel #7
0
 def update_contents_unpacked_etc(collection, **kwargs):
     deleted = kwargs.get('deleted', False)
     collection_id = kwargs.get('collection_id')
     if deleted:
         collection.contents = remove_references_to_this_collection(collection.contents, collection_id)
     collections_referenced = get_collections_referenced(collection.contents)
     collection.contents_unpacked = generate_contents_unpacked(
                                 collection.contents, collections_referenced)
     collection.html = h.get_HTML_from_contents(collection.contents_unpacked,
                                               collection.markup_language)
     collection.forms = [Session.query(Form).get(int(id)) for id in
                 h.form_reference_pattern.findall(collection.contents_unpacked)]
Beispiel #8
0
def update_user(user, data):
    """Update a user.

    :param user: the user model to be updated.
    :param dict data: representation of the updated user.
    :returns: the updated user model or, if ``changed`` has not been set
        to ``True``, ``False``.

    """
    changed = False

    # Unicode Data
    changed = user.set_attr('first_name', h.normalize(data['first_name']),
                            changed)
    changed = user.set_attr('last_name', h.normalize(data['last_name']),
                            changed)
    changed = user.set_attr('email', h.normalize(data['email']), changed)
    changed = user.set_attr('affiliation', h.normalize(data['affiliation']),
                            changed)
    changed = user.set_attr('role', h.normalize(data['role']), changed)
    changed = user.set_attr('page_content', h.normalize(data['page_content']),
                            changed)
    changed = user.set_attr('markup_language',
                            h.normalize(data['markup_language']), changed)
    changed = user.set_attr(
        'html',
        h.get_HTML_from_contents(user.page_content, user.markup_language),
        changed)

    # username and password need special treatment: a value of None means that
    # these should not be updated.
    if data['password'] is not None:
        changed = user.set_attr(
            'password',
            unicode(h.encrypt_password(data['password'], str(user.salt))),
            changed)
    if data['username'] is not None:
        username = h.normalize(data['username'])
        if username != user.username:
            h.rename_user_directory(user.username, username)
        changed = user.set_attr('username', username, changed)

    # Many-to-One Data
    changed = user.set_attr('input_orthography', data['input_orthography'],
                            changed)
    changed = user.set_attr('output_orthography', data['output_orthography'],
                            changed)

    if changed:
        user.datetime_modified = datetime.datetime.utcnow()
        return user
    return changed
Beispiel #9
0
    def test_create(self):
        """Tests that POST /pages creates a new page
        or returns an appropriate error if the input is invalid.
        """

        original_page_count = Session.query(Page).count()

        # Create a valid one
        params = self.page_create_params.copy()
        params.update({
            'name': u'page',
            'markup_language': u'Markdown',
            'content': self.md_contents
        })
        params = json.dumps(params)
        response = self.app.post(url('pages'), params, self.json_headers, self.extra_environ_admin)
        resp = json.loads(response.body)
        new_page_count = Session.query(Page).count()
        assert new_page_count == original_page_count + 1
        assert resp['name'] == u'page'
        assert resp['content'] == self.md_contents
        assert resp['html'] == h.get_HTML_from_contents(self.md_contents, 'Markdown')
        assert response.content_type == 'application/json'

        # Invalid because name is empty and markup language is invalid
        params = self.page_create_params.copy()
        params.update({
            'name': u'',
            'markup_language': u'markdownable',
            'content': self.md_contents
        })
        params = json.dumps(params)
        response = self.app.post(url('pages'), params, self.json_headers, self.extra_environ_admin, status=400)
        resp = json.loads(response.body)
        assert resp['errors']['name'] == u'Please enter a value'
        assert resp['errors']['markup_language'] == \
            u"Value must be one of: Markdown; reStructuredText (not u'markdownable')"
        assert response.content_type == 'application/json'

        # Invalid because name is too long
        params = self.page_create_params.copy()
        params.update({
            'name': u'name' * 200,
            'markup_language': u'Markdown',
            'content': self.md_contents
        })
        params = json.dumps(params)
        response = self.app.post(url('pages'), params, self.json_headers, self.extra_environ_admin, status=400)
        resp = json.loads(response.body)
        assert resp['errors']['name'] == u'Enter a value not more than 255 characters long'
        assert response.content_type == 'application/json'
Beispiel #10
0
def create_new_page(data):
    """Create a new page.

    :param dict data: the data for the page to be created.
    :returns: an SQLAlchemy model object representing the page.

    """
    page = Page()
    page.name = h.normalize(data['name'])
    page.heading = h.normalize(data['heading'])
    page.markup_language = data['markup_language']
    page.content = h.normalize(data['content'])
    page.html = h.get_HTML_from_contents(page.content, page.markup_language)
    page.datetime_modified = datetime.datetime.utcnow()
    return page
Beispiel #11
0
def create_new_page(data):
    """Create a new page.

    :param dict data: the data for the page to be created.
    :returns: an SQLAlchemy model object representing the page.

    """
    page = Page()
    page.name = h.normalize(data['name'])
    page.heading = h.normalize(data['heading'])
    page.markup_language = data['markup_language']
    page.content = h.normalize(data['content'])
    page.html = h.get_HTML_from_contents(page.content, page.markup_language)
    page.datetime_modified = datetime.datetime.utcnow()
    return page
Beispiel #12
0
 def update_contents_unpacked_etc(collection, **kwargs):
     deleted = kwargs.get('deleted', False)
     collection_id = kwargs.get('collection_id')
     if deleted:
         collection.contents = remove_references_to_this_collection(
             collection.contents, collection_id)
     collections_referenced = get_collections_referenced(
         collection.contents)
     collection.contents_unpacked = generate_contents_unpacked(
         collection.contents, collections_referenced)
     collection.html = h.get_HTML_from_contents(
         collection.contents_unpacked, collection.markup_language)
     collection.forms = [
         Session.query(Form).get(int(id)) for id in
         h.form_reference_pattern.findall(collection.contents_unpacked)
     ]
Beispiel #13
0
def create_new_speaker(data):
    """Create a new speaker.

    :param dict data: the data for the speaker to be created.
    :returns: an SQLAlchemy model object representing the speaker.

    """
    speaker = Speaker()
    speaker.first_name = h.normalize(data['first_name'])
    speaker.last_name = h.normalize(data['last_name'])
    speaker.dialect = h.normalize(data['dialect'])
    speaker.page_content = h.normalize(data['page_content'])
    speaker.datetime_modified = datetime.datetime.utcnow()
    speaker.markup_language = h.normalize(data['markup_language'])
    speaker.html = h.get_HTML_from_contents(speaker.page_content, speaker.markup_language)
    return speaker
Beispiel #14
0
def update_page(page, data):
    """Update a page.

    :param page: the page model to be updated.
    :param dict data: representation of the updated page.
    :returns: the updated page model or, if ``changed`` has not been set
        to ``True``, ``False``.

    """
    changed = False
    # Unicode Data
    changed = page.set_attr('name', h.normalize(data['name']), changed)
    changed = page.set_attr('heading', h.normalize(data['heading']), changed)
    changed = page.set_attr('markup_language', data['markup_language'], changed)
    changed = page.set_attr('content', h.normalize(data['content']), changed)
    changed = page.set_attr('html', h.get_HTML_from_contents(page.content, page.markup_language), changed)

    if changed:
        page.datetime_modified = datetime.datetime.utcnow()
        return page
    return changed
Beispiel #15
0
def update_user(user, data):
    """Update a user.

    :param user: the user model to be updated.
    :param dict data: representation of the updated user.
    :returns: the updated user model or, if ``changed`` has not been set
        to ``True``, ``False``.

    """
    changed = False

    # Unicode Data
    changed = user.set_attr('first_name', h.normalize(data['first_name']), changed)
    changed = user.set_attr('last_name', h.normalize(data['last_name']), changed)
    changed = user.set_attr('email', h.normalize(data['email']), changed)
    changed = user.set_attr('affiliation', h.normalize(data['affiliation']), changed)
    changed = user.set_attr('role', h.normalize(data['role']), changed)
    changed = user.set_attr('page_content', h.normalize(data['page_content']), changed)
    changed = user.set_attr('markup_language', h.normalize(data['markup_language']), changed)
    changed = user.set_attr('html', h.get_HTML_from_contents(user.page_content, user.markup_language), changed)

    # username and password need special treatment: a value of None means that
    # these should not be updated.
    if data['password'] is not None:
        changed = user.set_attr('password',
                    unicode(h.encrypt_password(data['password'], str(user.salt))), changed)
    if data['username'] is not None:
        username = h.normalize(data['username'])
        if username != user.username:
            h.rename_user_directory(user.username, username)
        changed = user.set_attr('username', username, changed)

    # Many-to-One Data
    changed = user.set_attr('input_orthography', data['input_orthography'], changed)
    changed = user.set_attr('output_orthography', data['output_orthography'], changed)

    if changed:
        user.datetime_modified = datetime.datetime.utcnow()
        return user
    return changed
Beispiel #16
0
def update_page(page, data):
    """Update a page.

    :param page: the page model to be updated.
    :param dict data: representation of the updated page.
    :returns: the updated page model or, if ``changed`` has not been set
        to ``True``, ``False``.

    """
    changed = False
    # Unicode Data
    changed = page.set_attr('name', h.normalize(data['name']), changed)
    changed = page.set_attr('heading', h.normalize(data['heading']), changed)
    changed = page.set_attr('markup_language', data['markup_language'],
                            changed)
    changed = page.set_attr('content', h.normalize(data['content']), changed)
    changed = page.set_attr(
        'html', h.get_HTML_from_contents(page.content, page.markup_language),
        changed)

    if changed:
        page.datetime_modified = datetime.datetime.utcnow()
        return page
    return changed
Beispiel #17
0
    def test_create(self):
        """Tests that POST /pages creates a new page
        or returns an appropriate error if the input is invalid.
        """

        original_page_count = Session.query(Page).count()

        # Create a valid one
        params = self.page_create_params.copy()
        params.update({
            'name': u'page',
            'markup_language': u'Markdown',
            'content': self.md_contents
        })
        params = json.dumps(params)
        response = self.app.post(url('pages'), params, self.json_headers,
                                 self.extra_environ_admin)
        resp = json.loads(response.body)
        new_page_count = Session.query(Page).count()
        assert new_page_count == original_page_count + 1
        assert resp['name'] == u'page'
        assert resp['content'] == self.md_contents
        assert resp['html'] == h.get_HTML_from_contents(
            self.md_contents, 'Markdown')
        assert response.content_type == 'application/json'

        # Invalid because name is empty and markup language is invalid
        params = self.page_create_params.copy()
        params.update({
            'name': u'',
            'markup_language': u'markdownable',
            'content': self.md_contents
        })
        params = json.dumps(params)
        response = self.app.post(url('pages'),
                                 params,
                                 self.json_headers,
                                 self.extra_environ_admin,
                                 status=400)
        resp = json.loads(response.body)
        assert resp['errors']['name'] == u'Please enter a value'
        assert resp['errors']['markup_language'] == \
            u"Value must be one of: Markdown; reStructuredText (not u'markdownable')"
        assert response.content_type == 'application/json'

        # Invalid because name is too long
        params = self.page_create_params.copy()
        params.update({
            'name': u'name' * 200,
            'markup_language': u'Markdown',
            'content': self.md_contents
        })
        params = json.dumps(params)
        response = self.app.post(url('pages'),
                                 params,
                                 self.json_headers,
                                 self.extra_environ_admin,
                                 status=400)
        resp = json.loads(response.body)
        assert resp['errors'][
            'name'] == u'Enter a value not more than 255 characters long'
        assert response.content_type == 'application/json'
Beispiel #18
0
def update_collection(collection, data, collections_referenced):
    """Update a collection model.

    :param collection: the collection model to be updated.
    :param dict data: representation of the updated collection.
    :param dict collections_referenced: the collection models recursively referenced in ``data['contents']``.
    :returns: a 3-tuple where the second and third elements are invariable
        booleans indicating whether the collection has become restricted or has
        had its ``contents`` value changed as a result of the update,
        respectively.  The first element is the updated collection or ``False``
        of the no update has occurred.

    """
    changed = False
    restricted = False
    contents_changed = False

    # Unicode Data
    changed = collection.set_attr('title', h.normalize(data['title']), changed)
    changed = collection.set_attr('type', h.normalize(data['type']), changed)
    changed = collection.set_attr('url', h.normalize(data['url']), changed)
    changed = collection.set_attr('description',
                                  h.normalize(data['description']), changed)
    changed = collection.set_attr('markup_language',
                                  h.normalize(data['markup_language']),
                                  changed)
    submitted_contents = h.normalize(data['contents'])
    if collection.contents != submitted_contents:
        collection.contents = submitted_contents
        contents_changed = changed = True
    changed = collection.set_attr('contents_unpacked',
                                  h.normalize(data['contents_unpacked']),
                                  changed)
    changed = collection.set_attr(
        'html',
        h.get_HTML_from_contents(collection.contents_unpacked,
                                 collection.markup_language), changed)

    # User-entered date: date_elicited
    changed = collection.set_attr('date_elicited', data['date_elicited'],
                                  changed)

    # Many-to-One Data
    changed = collection.set_attr('elicitor', data['elicitor'], changed)
    changed = collection.set_attr('speaker', data['speaker'], changed)
    changed = collection.set_attr('source', data['source'], changed)

    # Many-to-Many Data: files, forms & tags
    # Update only if the user has made changes.
    files_to_add = [f for f in data['files'] if f]
    forms_to_add = [f for f in data['forms'] if f]
    tags_to_add = [t for t in data['tags'] if t]

    if set(files_to_add) != set(collection.files):
        collection.files = files_to_add
        changed = True

    if set(forms_to_add) != set(collection.forms):
        collection.forms = forms_to_add
        changed = True

    # Restrict the entire collection if it is associated to restricted forms or
    # files or if it references a restricted collection.
    tags = [
        f.tags for f in collection.files + collection.forms +
        collections_referenced.values()
    ]
    tags = [tag for tag_list in tags for tag in tag_list]
    restricted_tags = [tag for tag in tags if tag.name == u'restricted']
    if restricted_tags:
        restricted_tag = restricted_tags[0]
        if restricted_tag not in tags_to_add:
            tags_to_add.append(restricted_tag)

    if set(tags_to_add) != set(collection.tags):
        if u'restricted' in [t.name for t in tags_to_add] and \
        u'restricted' not in [t.name for t in collection.tags]:
            restricted = True
        collection.tags = tags_to_add
        changed = True

    if changed:
        collection.datetime_modified = datetime.datetime.utcnow()
        session['user'] = Session.merge(session['user'])
        collection.modifier = session['user']
        return collection, restricted, contents_changed
    return changed, restricted, contents_changed
Beispiel #19
0
def create_new_collection(data, collections_referenced):
    """Create a new collection.

    :param dict data: the collection to be created.
    :param dict collections_referenced: the collection models recursively referenced in ``data['contents']``.
    :returns: an SQLAlchemy model object representing the collection.

    """
    collection = Collection()
    collection.UUID = unicode(uuid4())

    # User-inputted string data
    collection.title = h.normalize(data['title'])
    collection.type = h.normalize(data['type'])
    collection.url = h.normalize(data['url'])
    collection.description = h.normalize(data['description'])
    collection.markup_language = h.normalize(data['markup_language'])
    collection.contents = h.normalize(data['contents'])
    collection.contents_unpacked = h.normalize(data['contents_unpacked'])
    collection.html = h.get_HTML_from_contents(collection.contents_unpacked,
                                               collection.markup_language)

    # User-inputted date: date_elicited
    collection.date_elicited = data['date_elicited']

    # Many-to-One
    if data['elicitor']:
        collection.elicitor = data['elicitor']
    if data['speaker']:
        collection.speaker = data['speaker']
    if data['source']:
        collection.source = data['source']

    # Many-to-Many: tags, files & forms
    collection.tags = [t for t in data['tags'] if t]
    collection.files = [f for f in data['files'] if f]
    collection.forms = [f for f in data['forms'] if f]

    # Restrict the entire collection if it is associated to restricted forms or
    # files or if it references a restricted collection in its contents field.
    immediately_referenced_collections = get_collections_referenced_in_contents(
        collection, collections_referenced)
    tags = [
        f.tags for f in collection.files + collection.forms +
        immediately_referenced_collections
    ]
    tags = [tag for tag_list in tags for tag in tag_list]
    restricted_tags = [tag for tag in tags if tag.name == u'restricted']
    if restricted_tags:
        restricted_tag = restricted_tags[0]
        if restricted_tag not in collection.tags:
            collection.tags.append(restricted_tag)

    # OLD-generated Data
    now = datetime.datetime.utcnow()
    collection.datetime_entered = now
    collection.datetime_modified = now
    # Because of SQLAlchemy's uniqueness constraints, we may need to set the
    # enterer to the elicitor.
    if data['elicitor'] and (data['elicitor'].id == session['user'].id):
        collection.enterer = data['elicitor']
    else:
        collection.enterer = session['user']

    return collection
Beispiel #20
0
def update_collection(collection, data, collections_referenced):
    """Update a collection model.

    :param collection: the collection model to be updated.
    :param dict data: representation of the updated collection.
    :param dict collections_referenced: the collection models recursively referenced in ``data['contents']``.
    :returns: a 3-tuple where the second and third elements are invariable
        booleans indicating whether the collection has become restricted or has
        had its ``contents`` value changed as a result of the update,
        respectively.  The first element is the updated collection or ``False``
        of the no update has occurred.

    """
    changed = False
    restricted = False
    contents_changed = False

    # Unicode Data
    changed = collection.set_attr('title', h.normalize(data['title']), changed)
    changed = collection.set_attr('type', h.normalize(data['type']), changed)
    changed = collection.set_attr('url', h.normalize(data['url']), changed)
    changed = collection.set_attr('description', h.normalize(data['description']), changed)
    changed = collection.set_attr('markup_language', h.normalize(data['markup_language']), changed)
    submitted_contents = h.normalize(data['contents'])
    if collection.contents != submitted_contents:
        collection.contents = submitted_contents
        contents_changed = changed = True
    changed = collection.set_attr('contents_unpacked', h.normalize(data['contents_unpacked']), changed)
    changed = collection.set_attr('html', h.get_HTML_from_contents(collection.contents_unpacked,
                                                      collection.markup_language), changed)

    # User-entered date: date_elicited
    changed = collection.set_attr('date_elicited', data['date_elicited'], changed)

    # Many-to-One Data
    changed = collection.set_attr('elicitor', data['elicitor'], changed)
    changed = collection.set_attr('speaker', data['speaker'], changed)
    changed = collection.set_attr('source', data['source'], changed)

    # Many-to-Many Data: files, forms & tags
    # Update only if the user has made changes.
    files_to_add = [f for f in data['files'] if f]
    forms_to_add = [f for f in data['forms'] if f]
    tags_to_add = [t for t in data['tags'] if t]

    if set(files_to_add) != set(collection.files):
        collection.files = files_to_add
        changed = True

    if set(forms_to_add) != set(collection.forms):
        collection.forms = forms_to_add
        changed = True

    # Restrict the entire collection if it is associated to restricted forms or
    # files or if it references a restricted collection.
    tags = [f.tags for f in collection.files + collection.forms + collections_referenced.values()]
    tags = [tag for tag_list in tags for tag in tag_list]
    restricted_tags = [tag for tag in tags if tag.name == u'restricted']
    if restricted_tags:
        restricted_tag = restricted_tags[0]
        if restricted_tag not in tags_to_add:
            tags_to_add.append(restricted_tag)

    if set(tags_to_add) != set(collection.tags):
        if u'restricted' in [t.name for t in tags_to_add] and \
        u'restricted' not in [t.name for t in collection.tags]:
            restricted = True
        collection.tags = tags_to_add
        changed = True

    if changed:
        collection.datetime_modified = datetime.datetime.utcnow()
        session['user'] = Session.merge(session['user'])
        collection.modifier = session['user']
        return collection, restricted, contents_changed
    return changed, restricted, contents_changed