Matthew Schinckel avatar Matthew Schinckel committed 231875c

Stage 1 of revamp of create/update

Comments (0)

Files changed (1)

rest_api/options.py

 from django.utils.encoding import force_unicode
 from django.conf.urls.defaults import patterns, url
 from django.forms import ValidationError
+from django.core.exceptions import ObjectDoesNotExist
 
 from decorators import methods
 import http
         # Ensure that we have a list of objects, in the case we only recvd
         # a single object.
         data = request.data
-        if getattr(data, 'keys', False):
+        if not isinstance(data, (list, tuple)):
             data = [data]
-        
+    
         created = []
-        related = {}
         for incoming in data:
-            # We now need to ensure that any foreign key fields are objects,
-            # not the keys.
-            if incoming.has_key("id"):
-                incoming.pop("id")
-            for f in self.model._meta.fields:
-                if getattr(f, 'related', False):
-                    if f.name in incoming.keys():
-                        if f.null and incoming[f.name] == '':
-                            incoming[f.name] = None
-                        if incoming[f.name]:
-                            if not isinstance(incoming[f.name], f.rel.to):
-                                # Check to see if it is already an object.
-                                incoming[f.name] = f.rel.to.objects.get(pk=incoming[f.name])
-            for f in self.model._meta.get_all_related_objects():
-                # Not sure how m2m will go here.
-                if incoming.get(f.get_accessor_name(), False):
-                    related[f.get_accessor_name()] = incoming.pop(f.get_accessor_name())
-            try:
-                instance = self.model(**incoming)
-                # If there are audit fields, then update the values.
-                instance.created_by = request.user
-                instance.updated_by = request.user
-                instance.save()
-                # Examine related fields, as we will need to create
-                # new models, potentially with relations to other models.
-                if related:
-                    for accessor, obj in related.iteritems():
-                        relation = getattr(instance, accessor)
-                        model = relation.model
-                        if not isinstance(obj, list):
-                            obj = [obj]
-                        for x in obj:
-                            for k,v in x.iteritems():
-                                field = model._meta.get_field_by_name(k)[0]
-                                # If it is a related field, get the object,
-                                # as we cannot create it without it.
-                                if hasattr(field,'related'):
-                                    x[k] = field.rel.to.objects.get(pk=v)
-                            getattr(instance, accessor).create(**x)
-                created.append(instance.pk)
-            except (IntegrityError, TypeError, ValueError), arg:
-                transaction.rollback()
-                raise http.Conflict(arg.args[0])
+            self._create(request, incoming)
         objects = self.model.objects.filter(pk__in=created)
         for obj in objects:
             self.log_addition(request, obj)
         if len(objects):
             return http.Created(objects)
         return http.OK("Nothing created.")
-
+    
+    def _process_data(self, instance, data):
+        # Remove the primary key, in case we are creating a new object.
+        data.pop(instance._meta.pk.name, None)
+        
+        instance.__changed_fields = []
+        
+        many_to_many = {}
+        for key, value in data.iteritems():
+            try:
+                field, model.direct, m2m = instance.model._meta.get_field_by_name(key)
+            except FieldDoesNotExist:
+                continue
+            
+            # In the case of a non-direct field, we need to get the actual field.
+            if not direct:
+                field = field.field
+            if m2m:
+                many_to_many[field] = value
+            self._process_field(instance, field, value)
+        
+        instance.full_clean()
+        instance.save()
+        
+        for field, value in many_to_many.iteritems():
+            self._process_m2m_field(instance, field, value)
+        
+        return instance
+        
+        
+    def _process_field(self, instance, field, value):
+        if field.rel:
+            # Related object
+            if isinstance(value, dict):
+                # Nested object.
+                # If the included object has no pk, then we need to create
+                # a new one.
+                # Do we need to delete the old one: only if it is a OneToOne, not NULLABLE
+                pk = value.get(field.model._meta.pk.name, value.get('pk', None))
+                value[field.related.var_name] = self
+                if pk:
+                    # Already existing object
+                    self.site._get_api_for_model(field.model)._update(request, value, pk)
+                    value = pk
+                else:
+                    # New object
+                    value = self.site._get_api_for_model(field.model)._create(request, value).pk
+            else:
+                # PK only.
+                pass
+        else:
+            value = field.to_python(value)
+        
+        old_value = getattr(instance, field.attname)
+        
+        if old_value != value:
+            setattr(instance, field.attname, value)
+            instance.__changed_fields.append(field.name)
+        
+    def _process_m2m_field(self, instance, field, value):
+        getattr(instance, field.name).clear()
+        getattr(instance, field.name).add(value)
+            
+    def _update(self, request, data, pk):
+        instance = self.queryset(request).get(pk=pk)
+        instance.updated_by = request.user
+        return self._process_data(instance, data)
+    
+    def _create(self, request, data):
+        instance = self.model()
+        instance.created_by = request.user
+        instance.updated_by = request.user
+        return self._process_data(instance, data)
     
     def update(self, request, pk):
         qs = self.queryset(request).filter(pk=pk)
         if not qs.exists():
             return self.create(request)
         
-        if not hasattr(request.data, 'keys'):
-            # Might be a list
-            if len(request.data) == 1:
-                request.data = request.data[0]
-            else:
-                raise http.BadRequest('Cannot update an object with more than one item.')
-                
+        data = request.data
+        if not isinstance(data, (list, tuple)):
+            raise http.Forbidden("You may not pass multiple objects to update.")                
         instance = qs.get()
         if not self.has_change_permission(request, instance):
             raise http.Forbidden()
         
-        changed = {}
-        for key,value in request.data.iteritems():
-            # We don't mind if things are passed in that we don't know about.
-            # Just ignore them.
-            if not hasattr(instance, key):
-                continue
-            try:
-                field = instance._meta.get_field(key)
-                # We don't want to be changing primary keys of objects.
-                if field.primary_key:
-                    continue
-                # If this is a field that is automatically update, we can
-                # ignore the passed in value(s)
-                if getattr(field, 'auto_now', False) or getattr(field, 'auto_now_add', False):
-                    continue
-                # If this was an audit field, ignore it. We set these according to the user who ran the request.
-                if field.name == 'updated_by' or field.name == 'created_by':
-                    continue
-                if not field.empty_strings_allowed and field.null and value == "":
-                    # I hate that django wants to store "" in the database
-                    # when I really mean None.
-                    setattr(instance, key, None)
-                    continue
-                if field.value_from_object(instance) != field.to_python(value):
-                    # Do I need to serialize this?
-                    if field.rel:
-                        setattr(instance, field.attname, field.to_python(value))
-                        #TODO: set the changed value to the __unicode__ of the referenced object,
-                        # but this will require another database request for each change.
-                    else:
-                        setattr(instance, key, field.to_python(value))
-                    changed[key] = value
-            except FieldDoesNotExist:
-                # There was no field. There may be an attribute, that alters
-                # something, or a reverse relation on a ForeignKey
-                if hasattr(getattr(instance, key), 'all'):
-                    # Maybe a ForeignKey?
-                    # For now, just delete all of the values, and
-                    # create new ones. Eventually, we want to be able to
-                    # compare what is already there, and mark changes.
-                    getattr(instance, key).all().delete()
-                    for val in value:
-                        for k,v in val.iteritems():
-                            f = getattr(instance, key).model._meta.get_field(k)
-                            if f.primary_key:
-                                del val[k]
-                                continue
-                            if getattr(f, 'auto_now', False) or getattr(f, 'auto_now_add', False):
-                                del val[k]
-                                continue
-                            if f.name == 'updated_by':
-                                val[k] = request.user
-                                continue
-                            if f.name == 'created_by':
-                                val[k] = request.user
-                                continue
-                            if f.rel:
-                                val[k] = f.rel.to.objects.get(pk=v)
-                        getattr(instance, key).create(**val)
-                elif getattr(instance, key) != value:
-                    try:
-                        setattr(instance, key, value)
-                        changed[key] = value
-                    except AttributeError:
-                        # Could not set this attribute: read-only?
-                        pass
-                
-        instance.updated_by = request.user
-        try:
-            instance.save()
-        except IntegrityError, arg:
-            transaction.rollback()
-            raise http.Conflict(arg.args[0])
-
+        self._update(request, data, pk)
         if len(changed):
             message = "Changed %s." % ", ".join(["%s to %s" % x for x in changed.iteritems()])
             #TODO: instance should reflect the new values?
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.