diff --git a/gracedb/superevents/api/serializers.py b/gracedb/superevents/api/serializers.py
index 1e47d7c01a56e68b1f10a2ed7ebc908321c87343..bb05cd0dbd248314f7cd249560a02b36205ee21a 100644
--- a/gracedb/superevents/api/serializers.py
+++ b/gracedb/superevents/api/serializers.py
@@ -5,7 +5,8 @@ from django.conf import settings
 from ..models import Superevent, Labelling, Log, VOEvent, EMObservation
 from ..forms import LogCreateForm
 from ..utils import create_superevent, update_superevent, add_tag_to_log, \
-    create_log, add_event_to_superevent, add_label_to_superevent
+    create_log, add_event_to_superevent, add_label_to_superevent, \
+    get_or_create_tag, get_or_create_tags
 
 from .fields import ParentObjectDefault
 from .settings import SUPEREVENT_LOOKUP_FIELD
@@ -120,6 +121,8 @@ class SupereventUpdateSerializer(SupereventSerializer):
         self.fields['labels'].read_only = True
 
     def validate(self, data):
+        # We don't want to use the SupereventSerializers validate method, which
+        # is why we use that class in the super() call here
         data = super(SupereventSerializer, self).validate(data)
         preferred_event = data.get('preferred_event')
 
@@ -139,7 +142,11 @@ class SupereventUpdateSerializer(SupereventSerializer):
         return data
 
     def update(self, instance, validated_data):
-        updater = validated_data.pop('user')
+        # CurrentUserDefault doesn't work for PATCH requests since the
+        # serializer has self.partial == True, the default function is never
+        # called to fill empty data values. So we just grab the user directly
+        # from the request.
+        updater = self.context['request'].user
         instance = update_superevent(instance, updater, issue_alert=True,
             **validated_data)
         return instance
@@ -220,27 +227,36 @@ class SupereventLabelSerializer(serializers.ModelSerializer):
 
 
 class SupereventLogSerializer(serializers.ModelSerializer):
+    # Error messages
+    default_error_messages = {
+        'bad_display': _('There should either be as many display names as '
+            'tags, or there should be no display names'),
+    }
     # Read only fields
     self = serializers.SerializerMethodField(read_only=True)
     created = serializers.DateTimeField(format=settings.GRACE_STRFTIME_FORMAT,
         read_only=True)
     issuer = serializers.SlugRelatedField(slug_field='username',
         read_only=True)
-    # Both
-    tag_names = serializers.SlugRelatedField(slug_field='name',
-        queryset=Tag.objects.all(), many=True, source='tags')
+    tag_names = serializers.SlugRelatedField(slug_field='name', many=True,
+        source='tags', read_only=True)
     # Write only fields (submitter used to set creator for created instance)
-    data_file = serializers.FileField(label='File', write_only=True,
+    upload = serializers.FileField(label='File', write_only=True,
         required=False)
     submitter = serializers.HiddenField(write_only=True,
         default=serializers.CurrentUserDefault())
     superevent = serializers.HiddenField(write_only=True,
         default=ParentObjectDefault(context_key='superevent'))
+    tagname = serializers.ListField(write_only=True, label='Tag names',
+        child=serializers.CharField(), required=False)
+    displayName = serializers.ListField(write_only=True, label='Display names',
+        child=serializers.CharField(), required=False)
 
     class Meta:
         model = Log
         fields = ('self', 'N', 'comment', 'created', 'issuer', 'filename',
-            'file_version', 'tag_names', 'submitter', 'superevent', 'data_file')
+            'file_version', 'tag_names', 'submitter', 'superevent',
+            'tagname', 'displayName', 'upload')
 
     def __init__(self, *args, **kwargs):
         super(SupereventLogSerializer, self).__init__(*args, **kwargs)
@@ -253,21 +269,40 @@ class SupereventLogSerializer(serializers.ModelSerializer):
         return gracedb_reverse('superevent-log-detail', args=[
             superevent_id, obj.N], request=self.context['request'])
 
+    def validate(self, data):
+        data = super(SupereventLogSerializer, self).validate(data)
+        tags = data.get('tagname', None)
+        tag_displays = data.get('displayName', None)
+
+        if (tags and tag_displays) and (len(tags) != len(tag_displays)):
+            self.fail('bad_display')
+
+        return data
+
     def create(self, validated_data):
         # Check user permissions here, or somewhere else? Maybe just on viewset
         # create resource
+        logger.debug(validated_data)
+
+        # Convert data to be used with create_log function
+        issuer = validated_data.get('submitter', None)
+        superevent = validated_data.get('superevent', None)
+        data_file = validated_data.get('upload', None)
+        filename = getattr(data_file, 'name', "")
+        tag_names = validated_data.get('tagname', [])
+        display_names = validated_data.get('displayName', [])
 
-        # Convert to be used with Django form for Logs
-        validated_data['issuer'] = validated_data.pop('submitter').id
-        validated_data['superevent'] = validated_data['superevent'].id
-        if validated_data.has_key('data_file'):
-            validated_data['filename'] = validated_data['data_file'].name
+        # Get tag objects
+        tags = []
+        if tag_names:
+            tags = get_or_create_tags(tag_names, display_names)
 
-        form = LogCreateForm(validated_data, validated_data)
-        if form.is_valid():
-            obj = form.save()
+        # Create log message
+        log = create_log(issuer, validated_data['comment'], superevent,
+            filename=filename, data_file=data_file, tags=tags,
+            issue_alert=True, autogenerated=False)
 
-        return obj
+        return log
 
 
 class SupereventLogTagSerializer(serializers.ModelSerializer):
@@ -320,14 +355,9 @@ class SupereventLogTagSerializer(serializers.ModelSerializer):
         # Get parent log message
         parent_log = validated_data.pop('parent_log')
 
-        # get_or_create tag by name only
-        tag, created = self.Meta.model.objects.get_or_create(
-            name=validated_data['name'])
-
-        # If it's a new tag, set the displayName
-        if created and validated_data.has_key('displayName'):
-            tag.displayName = validated_data['displayName']
-            tag.save()
+        # get or create tag by name only
+        tag = get_or_create_tag(validated_data['name'],
+            validated_data.get('displayName', None))
 
         # Add tag to log
         add_tag_to_log(parent_log, tag, self.context.get('request').user,
@@ -338,25 +368,11 @@ class SupereventLogTagSerializer(serializers.ModelSerializer):
 
 class SupereventVOEventSerializer(serializers.ModelSerializer):
     # Read only fields
-    #self = serializers.SerializerMethodField(read_only=True)
-    #created = serializers.DateTimeField(format=settings.GRACE_STRFTIME_FORMAT,
-    #    read_only=True)
-    #issuer = serializers.SlugRelatedField(slug_field='username',
-    #    read_only=True)
-    ## Both
-    #tag_names = serializers.SlugRelatedField(slug_field='name',
-    #    queryset=Tag.objects.all(), many=True, source='tags')
-    ## Write only fields (submitter used to set creator for created instance)
-    #data_file = serializers.FileField(label='File', write_only=True,
-    #    required=False)
-    #submitter = serializers.HiddenField(write_only=True,
-    #    default=serializers.CurrentUserDefault())
-    #superevent = serializers.HiddenField(write_only=True,
-    #    default=ParentObjectDefault(context_key='superevent'))
     issuer = serializers.SlugRelatedField(slug_field='username',
         read_only=True)
+    created = serializers.DateTimeField(format=settings.GRACE_STRFTIME_FORMAT,
+        read_only=True)
     links = serializers.SerializerMethodField(read_only=True)
-    #text = serializers.SerializerMethodField(read_only=True)
 
     class Meta:
         model = VOEvent