Ticket #13914: django_13914_group_natural_keys.py

File django_13914_group_natural_keys.py, 4.7 KB (added by Walter Doekes, 10 years ago)

Backport of natural group keys for older Django

Line 
1# vim: set ts=8 sw=4 sts=4 et ai:
2"""
3Add natural keys to contrib.auth.User and Group models.
4https://code.djangoproject.com/ticket/13914
5
6In this case, only to the Group model we won't create duplicate groups
7without having to hardcode the Group PKs.
8
9In project.fixtures.initial_data.xml we define which groups have which
10permissions. But the extension modules will require additional groups
11to be created. They cannot safely choose a PK without stomping on
12different extension PKs.
13
14The fix: remove the PK from the fixtures.
15
16Ticket #13914 has been closed, the fix has been committed as
17954e3b4ad37b572cdc46baf3e35c712f4049efea in branch 1.7b4.
18"""
19from django import VERSION
20
21if VERSION < (1, 7):
22 from django.contrib.auth import models as auth_models
23 from django.db import models as db_models
24
25 class GroupManagerWithNaturalKey(db_models.Manager):
26 def get_by_natural_key(self, name):
27 return self.get(name=name)
28 #auth_models.Group.objects = GroupManagerWithNaturalKey()
29 auth_models.Group._default_manager = GroupManagerWithNaturalKey()
30 auth_models.Group._default_manager.model = auth_models.Group
31
32 def group_natural_key(self):
33 return (self.name,)
34 auth_models.Group.natural_key = group_natural_key
35
36 # But wait, there is more. We also need to patch the xml
37 # Deserializer to accept the natural key as PK.
38 from django.core.serializers import base, xml_serializer
39 from django.db import models
40
41 # This function is stolen from Django 1.7.
42 def build_instance(Model, data, db):
43 """
44 Build a model instance.
45
46 If the model instance doesn't have a primary key and the model supports
47 natural keys, try to retrieve it from the database.
48 """
49 obj = Model(**data)
50 if (obj.pk is None and hasattr(Model, 'natural_key') and
51 hasattr(Model._default_manager, 'get_by_natural_key')):
52 natural_key = obj.natural_key()
53 try:
54 obj.pk = Model._default_manager.db_manager(db).get_by_natural_key(*natural_key).pk
55 except Model.DoesNotExist:
56 pass
57 return obj
58
59 # This function is unchanged from
60 # django.core.serializers.xml_serializer.Deserializer
61 # apart from the build_instance code at the bottom.
62 def deserializer_handle_object(self, node):
63 """
64 Convert an <object> node to a DeserializedObject.
65 """
66 # Look up the model using the model loading mechanism. If this fails,
67 # bail.
68 Model = self._get_model_from_node(node, "model")
69
70 # Start building a data dictionary from the object.
71 # If the node is missing the pk set it to None
72 if node.hasAttribute("pk"):
73 pk = node.getAttribute("pk")
74 else:
75 pk = None
76
77 data = {Model._meta.pk.attname: Model._meta.pk.to_python(pk)}
78
79 # Also start building a dict of m2m data (this is saved as
80 # {m2m_accessor_attribute : [list_of_related_objects]})
81 m2m_data = {}
82
83 # Deseralize each field.
84 for field_node in node.getElementsByTagName("field"):
85 # If the field is missing the name attribute, bail (are you
86 # sensing a pattern here?)
87 field_name = field_node.getAttribute("name")
88 if not field_name:
89 raise base.DeserializationError("<field> node is missing the 'name' attribute")
90
91 # Get the field from the Model. This will raise a
92 # FieldDoesNotExist if, well, the field doesn't exist, which will
93 # be propagated correctly.
94 field = Model._meta.get_field(field_name)
95
96 # As is usually the case, relation fields get the special treatment.
97 if field.rel and isinstance(field.rel, models.ManyToManyRel):
98 m2m_data[field.name] = self._handle_m2m_field_node(field_node, field)
99 elif field.rel and isinstance(field.rel, models.ManyToOneRel):
100 data[field.attname] = self._handle_fk_field_node(field_node, field)
101 else:
102 if field_node.getElementsByTagName('None'):
103 value = None
104 else:
105 value = field.to_python(xml_serializer.getInnerText(field_node).strip())
106 data[field.name] = value
107
108 # Original.
109 # # Return a DeserializedObject so that the m2m data has a place to live.
110 # return base.DeserializedObject(Model(**data), m2m_data)
111
112 # New.
113 obj = build_instance(Model, data, self.db)
114 # Return a DeserializedObject so that the m2m data has a place to live.
115 return base.DeserializedObject(obj, m2m_data)
116
117 # Overwrite the old method with the new.
118 xml_serializer.Deserializer._handle_object = deserializer_handle_object
Back to Top