diff options
| author | Jason Pellerin <jpellerin@gmail.com> | 2006-06-28 17:07:26 +0000 |
|---|---|---|
| committer | Jason Pellerin <jpellerin@gmail.com> | 2006-06-28 17:07:26 +0000 |
| commit | da5b0586d359390e2102703707afae65befe7f6d (patch) | |
| tree | 53ab9ed62c0b7a8451355a34d6f1e0fab2f98af0 /django | |
| parent | 54b6e969571576e9ec616df8b074f744009b35cc (diff) | |
Merge trunk to [3226]
git-svn-id: http://code.djangoproject.com/svn/django/branches/multiple-db-support@3228 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django')
| -rw-r--r-- | django/conf/global_settings.py | 6 | ||||
| -rw-r--r-- | django/contrib/admin/views/decorators.py | 16 | ||||
| -rw-r--r-- | django/contrib/auth/__init__.py | 69 | ||||
| -rw-r--r-- | django/contrib/auth/backends.py | 21 | ||||
| -rw-r--r-- | django/contrib/auth/forms.py | 16 | ||||
| -rw-r--r-- | django/contrib/auth/middleware.py | 8 | ||||
| -rw-r--r-- | django/contrib/auth/models.py | 23 | ||||
| -rw-r--r-- | django/contrib/auth/views.py | 7 | ||||
| -rw-r--r-- | django/contrib/comments/views/comments.py | 4 | ||||
| -rw-r--r-- | django/contrib/markup/templatetags/markup.py | 2 | ||||
| -rw-r--r-- | django/core/serializers/__init__.py | 76 | ||||
| -rw-r--r-- | django/core/serializers/base.py | 159 | ||||
| -rw-r--r-- | django/core/serializers/xml_serializer.py | 218 | ||||
| -rw-r--r-- | django/db/models/base.py | 8 | ||||
| -rw-r--r-- | django/db/models/fields/__init__.py | 2 | ||||
| -rw-r--r-- | django/db/models/loading.py | 13 | ||||
| -rw-r--r-- | django/db/models/manager.py | 7 | ||||
| -rw-r--r-- | django/db/models/query.py | 9 |
18 files changed, 615 insertions, 49 deletions
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 640b290a6f..a1dff3c815 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -281,3 +281,9 @@ COMMENTS_FIRST_FEW = 0 # A tuple of IP addresses that have been banned from participating in various # Django-powered features. BANNED_IPS = () + +################## +# AUTHENTICATION # +################## + +AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',) diff --git a/django/contrib/admin/views/decorators.py b/django/contrib/admin/views/decorators.py index d984077dfb..250c585220 100644 --- a/django/contrib/admin/views/decorators.py +++ b/django/contrib/admin/views/decorators.py @@ -1,6 +1,7 @@ from django import http, template from django.conf import settings -from django.contrib.auth.models import User, SESSION_KEY +from django.contrib.auth.models import User +from django.contrib.auth import authenticate, login from django.shortcuts import render_to_response from django.utils.translation import gettext_lazy import base64, datetime, md5 @@ -69,10 +70,10 @@ def staff_member_required(view_func): return _display_login_form(request, message) # Check the password. - username = request.POST.get('username', '') - try: - user = User.objects.get(username=username, is_staff=True) - except User.DoesNotExist: + username = request.POST.get('username', None) + password = request.POST.get('password', None) + user = authenticate(username=username, password=password) + if user is None: message = ERROR_MESSAGE if '@' in username: # Mistakenly entered e-mail address instead of username? Look it up. @@ -86,8 +87,9 @@ def staff_member_required(view_func): # The user data is correct; log in the user in and continue. else: - if user.check_password(request.POST.get('password', '')): - request.session[SESSION_KEY] = user.id + if user.is_staff: + login(request, user) + # TODO: set last_login with an event. user.last_login = datetime.datetime.now() user.save() if request.POST.has_key('post_data'): diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py index ac7b40aca6..dde7ea5c9c 100644 --- a/django/contrib/auth/__init__.py +++ b/django/contrib/auth/__init__.py @@ -1,2 +1,71 @@ +from django.core.exceptions import ImproperlyConfigured + +SESSION_KEY = '_auth_user_id' +BACKEND_SESSION_KEY = '_auth_user_backend' LOGIN_URL = '/accounts/login/' REDIRECT_FIELD_NAME = 'next' + +def load_backend(path): + i = path.rfind('.') + module, attr = path[:i], path[i+1:] + try: + mod = __import__(module, '', '', [attr]) + except ImportError, e: + raise ImproperlyConfigured, 'Error importing authentication backend %s: "%s"' % (module, e) + try: + cls = getattr(mod, attr) + except AttributeError: + raise ImproperlyConfigured, 'Module "%s" does not define a "%s" authentication backend' % (module, attr) + return cls() + +def get_backends(): + from django.conf import settings + backends = [] + for backend_path in settings.AUTHENTICATION_BACKENDS: + backends.append(load_backend(backend_path)) + return backends + +def authenticate(**credentials): + """ + If the given credentials, return a user object. + """ + for backend in get_backends(): + try: + user = backend.authenticate(**credentials) + except TypeError: + # this backend doesn't accept these credentials as arguments, try the next one. + continue + if user is None: + continue + # annotate the user object with the path of the backend + user.backend = str(backend.__class__) + return user + +def login(request, user): + """ + Persist a user id and a backend in the request. This way a user doesn't + have to reauthenticate on every request. + """ + if user is None: + user = request.user + # TODO: It would be nice to support different login methods, like signed cookies. + request.session[SESSION_KEY] = user.id + request.session[BACKEND_SESSION_KEY] = user.backend + +def logout(request): + """ + Remove the authenticated user's id from request. + """ + del request.session[SESSION_KEY] + del request.session[BACKEND_SESSION_KEY] + +def get_user(request): + from django.contrib.auth.models import AnonymousUser + try: + user_id = request.session[SESSION_KEY] + backend_path = request.session[BACKEND_SESSION_KEY] + backend = load_backend(backend_path) + user = backend.get_user(user_id) or AnonymousUser() + except KeyError: + user = AnonymousUser() + return user diff --git a/django/contrib/auth/backends.py b/django/contrib/auth/backends.py new file mode 100644 index 0000000000..3b46b65b0a --- /dev/null +++ b/django/contrib/auth/backends.py @@ -0,0 +1,21 @@ +from django.contrib.auth.models import User, check_password + +class ModelBackend: + """ + Authenticate against django.contrib.auth.models.User + """ + # TODO: Model, login attribute name and password attribute name should be + # configurable. + def authenticate(self, username=None, password=None): + try: + user = User.objects.get(username=username) + if user.check_password(password): + return user + except User.DoesNotExist: + return None + + def get_user(self, user_id): + try: + return User.objects.get(pk=user_id) + except User.DoesNotExist: + return None diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index 800c14375b..ef81268e2a 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -1,4 +1,5 @@ from django.contrib.auth.models import User +from django.contrib.auth import authenticate from django.contrib.sites.models import Site from django.template import Context, loader from django.core import validators @@ -20,8 +21,7 @@ class AuthenticationForm(forms.Manipulator): self.fields = [ forms.TextField(field_name="username", length=15, maxlength=30, is_required=True, validator_list=[self.isValidUser, self.hasCookiesEnabled]), - forms.PasswordField(field_name="password", length=15, maxlength=30, is_required=True, - validator_list=[self.isValidPasswordForUser]), + forms.PasswordField(field_name="password", length=15, maxlength=30, is_required=True), ] self.user_cache = None @@ -30,16 +30,10 @@ class AuthenticationForm(forms.Manipulator): raise validators.ValidationError, _("Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in.") def isValidUser(self, field_data, all_data): - try: - self.user_cache = User.objects.get(username=field_data) - except User.DoesNotExist: - raise validators.ValidationError, _("Please enter a correct username and password. Note that both fields are case-sensitive.") - - def isValidPasswordForUser(self, field_data, all_data): + username = field_data + password = all_data.get('password', None) + self.user_cache = authenticate(username=username, password=password) if self.user_cache is None: - return - if not self.user_cache.check_password(field_data): - self.user_cache = None raise validators.ValidationError, _("Please enter a correct username and password. Note that both fields are case-sensitive.") elif not self.user_cache.is_active: raise validators.ValidationError, _("This account is inactive.") diff --git a/django/contrib/auth/middleware.py b/django/contrib/auth/middleware.py index a1a0b2e834..a6a60780a7 100644 --- a/django/contrib/auth/middleware.py +++ b/django/contrib/auth/middleware.py @@ -4,12 +4,8 @@ class LazyUser(object): def __get__(self, request, obj_type=None): if self._user is None: - from django.contrib.auth.models import User, AnonymousUser, SESSION_KEY - try: - user_id = request.session[SESSION_KEY] - self._user = User.objects.get(pk=user_id) - except (KeyError, User.DoesNotExist): - self._user = AnonymousUser() + from django.contrib.auth import get_user + self._user = get_user(request) return self._user class AuthenticationMiddleware(object): diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index 65392695ae..e37f5a4497 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -4,7 +4,19 @@ from django.contrib.contenttypes.models import ContentType from django.utils.translation import gettext_lazy as _ import datetime -SESSION_KEY = '_auth_user_id' +def check_password(raw_password, enc_password): + """ + Returns a boolean of whether the raw_password was correct. Handles + encryption formats behind the scenes. + """ + algo, salt, hsh = enc_password.split('$') + if algo == 'md5': + import md5 + return hsh == md5.new(salt+raw_password).hexdigest() + elif algo == 'sha1': + import sha + return hsh == sha.new(salt+raw_password).hexdigest() + raise ValueError, "Got unknown password algorithm type in password." class SiteProfileNotAvailable(Exception): pass @@ -141,14 +153,7 @@ class User(models.Model): self.set_password(raw_password) self.save() return is_correct - algo, salt, hsh = self.password.split('$') - if algo == 'md5': - import md5 - return hsh == md5.new(salt+raw_password).hexdigest() - elif algo == 'sha1': - import sha - return hsh == sha.new(salt+raw_password).hexdigest() - raise ValueError, "Got unknown password algorithm type in password." + return check_password(raw_password, self.password) def get_group_permissions(self): "Returns a list of permission strings that this user has through his/her groups." diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index 6d908ee025..97ecd6a3fd 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -3,7 +3,6 @@ from django.contrib.auth.forms import PasswordResetForm, PasswordChangeForm from django import forms from django.shortcuts import render_to_response from django.template import RequestContext -from django.contrib.auth.models import SESSION_KEY from django.contrib.sites.models import Site from django.http import HttpResponse, HttpResponseRedirect from django.contrib.auth.decorators import login_required @@ -19,7 +18,8 @@ def login(request, template_name='registration/login.html'): # Light security check -- make sure redirect_to isn't garbage. if not redirect_to or '://' in redirect_to or ' ' in redirect_to: redirect_to = '/accounts/profile/' - request.session[SESSION_KEY] = manipulator.get_user_id() + from django.contrib.auth import login + login(request, manipulator.get_user()) request.session.delete_test_cookie() return HttpResponseRedirect(redirect_to) else: @@ -33,8 +33,9 @@ def login(request, template_name='registration/login.html'): def logout(request, next_page=None, template_name='registration/logged_out.html'): "Logs out the user and displays 'You are logged out' message." + from django.contrib.auth import logout try: - del request.session[SESSION_KEY] + logout(request) except KeyError: return render_to_response(template_name, {'title': _('Logged out')}, context_instance=RequestContext(request)) else: diff --git a/django/contrib/comments/views/comments.py b/django/contrib/comments/views/comments.py index 316f3e719b..c32a82f4d8 100644 --- a/django/contrib/comments/views/comments.py +++ b/django/contrib/comments/views/comments.py @@ -5,7 +5,6 @@ from django.http import Http404 from django.core.exceptions import ObjectDoesNotExist from django.shortcuts import render_to_response from django.template import RequestContext -from django.contrib.auth.models import SESSION_KEY from django.contrib.comments.models import Comment, FreeComment, PHOTOS_REQUIRED, PHOTOS_OPTIONAL, RATINGS_REQUIRED, RATINGS_OPTIONAL, IS_PUBLIC from django.contrib.contenttypes.models import ContentType from django.contrib.auth.forms import AuthenticationForm @@ -219,7 +218,8 @@ def post_comment(request): # If user gave correct username/password and wasn't already logged in, log them in # so they don't have to enter a username/password again. if manipulator.get_user() and new_data.has_key('password') and manipulator.get_user().check_password(new_data['password']): - request.session[SESSION_KEY] = manipulator.get_user_id() + from django.contrib.auth import login + login(request, manipulator.get_user()) if errors or request.POST.has_key('preview'): class CommentFormWrapper(forms.FormWrapper): def __init__(self, manipulator, new_data, errors, rating_choices): diff --git a/django/contrib/markup/templatetags/markup.py b/django/contrib/markup/templatetags/markup.py index dc8a9da031..9cbf1b3807 100644 --- a/django/contrib/markup/templatetags/markup.py +++ b/django/contrib/markup/templatetags/markup.py @@ -27,7 +27,7 @@ def textile(value): raise template.TemplateSyntaxError, "Error in {% textile %} filter: The Python textile library isn't installed." return value else: - return textile.textile(value) + return textile.textile(value, encoding=settings.DEFAULT_CHARSET, output=settings.DEFAULT_CHARSET) def markdown(value): try: diff --git a/django/core/serializers/__init__.py b/django/core/serializers/__init__.py new file mode 100644 index 0000000000..72c4407b59 --- /dev/null +++ b/django/core/serializers/__init__.py @@ -0,0 +1,76 @@ +""" +Interfaces for serializing Django objects. + +Usage:: + + >>> from django.core import serializers + >>> json = serializers.serialize("json", some_query_set) + >>> objects = list(serializers.deserialize("json", json)) + +To add your own serializers, use the SERIALIZATION_MODULES setting:: + + SERIALIZATION_MODULES = { + "csv" : "path.to.csv.serializer", + "txt" : "path.to.txt.serializer", + } + +""" + +from django.conf import settings + +# Built-in serializers +BUILTIN_SERIALIZERS = { + "xml" : "django.core.serializers.xml_serializer", +} + +_serializers = {} + +def register_serializer(format, serializer_module): + """Register a new serializer by passing in a module name.""" + module = __import__(serializer_module, '', '', ['']) + _serializers[format] = module + +def unregister_serializer(format): + """Unregister a given serializer""" + del _serializers[format] + +def get_serializer(format): + if not _serializers: + _load_serializers() + return _serializers[format].Serializer + +def get_deserializer(format): + if not _serializers: + _load_serializers() + return _serializers[format].Deserializer + +def serialize(format, queryset, **options): + """ + Serialize a queryset (or any iterator that returns database objects) using + a certain serializer. + """ + s = get_serializer(format)() + s.serialize(queryset, **options) + return s.getvalue() + +def deserialize(format, stream_or_string): + """ + Deserialize a stream or a string. Returns an iterator that yields ``(obj, + m2m_relation_dict)``, where ``obj`` is a instantiated -- but *unsaved* -- + object, and ``m2m_relation_dict`` is a dictionary of ``{m2m_field_name : + list_of_related_objects}``. + """ + d = get_deserializer(format) + return d(stream_or_string) + +def _load_serializers(): + """ + Register built-in and settings-defined serializers. This is done lazily so + that user code has a chance to (e.g.) set up custom settings without + needing to be careful of import order. + """ + for format in BUILTIN_SERIALIZERS: + register_serializer(format, BUILTIN_SERIALIZERS[format]) + if hasattr(settings, "SERIALIZATION_MODULES"): + for format in settings.SERIALIZATION_MODULES: + register_serializer(format, settings.SERIALIZATION_MODULES[format])
\ No newline at end of file diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py new file mode 100644 index 0000000000..5c84861326 --- /dev/null +++ b/django/core/serializers/base.py @@ -0,0 +1,159 @@ +""" +Module for abstract serializer/unserializer base classes. +""" + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO +from django.db import models + +class SerializationError(Exception): + """Something bad happened during serialization.""" + pass + +class DeserializationError(Exception): + """Something bad happened during deserialization.""" + pass + +class Serializer(object): + """ + Abstract serializer base class. + """ + + def serialize(self, queryset, **options): + """ + Serialize a queryset. + """ + self.options = options + + self.stream = options.get("stream", StringIO()) + + self.start_serialization() + for obj in queryset: + self.start_object(obj) + for field in obj._meta.fields: + if field.rel is None: + self.handle_field(obj, field) + else: + self.handle_fk_field(obj, field) + for field in obj._meta.many_to_many: + self.handle_m2m_field(obj, field) + self.end_object(obj) + self.end_serialization() + return self.getvalue() + + def get_string_value(self, obj, field): + """ + Convert a field's value to a string. + """ + if isinstance(field, models.DateTimeField): + value = getattr(obj, field.name).strftime("%Y-%m-%d %H:%M:%S") + elif isinstance(field, models.FileField): + value = getattr(obj, "get_%s_url" % field.name, lambda: None)() + else: + value = field.flatten_data(follow=None, obj=obj).get(field.name, "") + return str(value) + + def start_serialization(self): + """ + Called when serializing of the queryset starts. + """ + raise NotImplementedError + + def end_serialization(self): + """ + Called when serializing of the queryset ends. + """ + pass + + def start_object(self, obj): + """ + Called when serializing of an object starts. + """ + raise NotImplementedError + + def end_object(self, obj): + """ + Called when serializing of an object ends. + """ + pass + + def handle_field(self, obj, field): + """ + Called to handle each individual (non-relational) field on an object. + """ + raise NotImplementedError + + def handle_fk_field(self, obj, field): + """ + Called to handle a ForeignKey field. + """ + raise NotImplementedError + + def handle_m2m_field(self, obj, field): + """ + Called to handle a ManyToManyField. + """ + raise NotImplementedError + + def getvalue(self): + """ + Return the fully serialized queryset. + """ + return self.stream.getvalue() + +class Deserializer(object): + """ + Abstract base deserializer class. + """ + + def __init__(self, stream_or_string, **options): + """ + Init this serializer given a stream or a string + """ + self.options = options + if isinstance(stream_or_string, basestring): + self.stream = StringIO(stream_or_string) + else: + self.stream = stream_or_string + # hack to make sure that the models have all been loaded before + # deserialization starts (otherwise subclass calls to get_model() + # and friends might fail...) + models.get_apps() + + def __iter__(self): + return self + + def next(self): + """Iteration iterface -- return the next item in the stream""" + raise NotImplementedError + +class DeserializedObject(object): + """ + A deserialzed model. + + Basically a container for holding the pre-saved deserialized data along + with the many-to-many data saved with the object. + + Call ``save()`` to save the object (with the many-to-many data) to the + database; call ``save(save_m2m=False)`` to save just the object fields + (and not touch the many-to-many stuff.) + """ + + def __init__(self, obj, m2m_data=None): + self.object = obj + self.m2m_data = m2m_data + + def __repr__(self): + return "<DeserializedObject: %s>" % str(self.object) + + def save(self, save_m2m=True): + self.object.save() + if self.m2m_data and save_m2m: + for accessor_name, object_list in self.m2m_data.items(): + setattr(self.object, accessor_name, object_list) + + # prevent a second (possibly accidental) call to save() from saving + # the m2m data twice. + self.m2m_data = None diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py new file mode 100644 index 0000000000..ab8769f237 --- /dev/null +++ b/django/core/serializers/xml_serializer.py @@ -0,0 +1,218 @@ +""" +XML serializer. +""" + +from xml.dom import pulldom +from django.utils.xmlutils import SimplerXMLGenerator +from django.core.serializers import base +from django.db import models + +class Serializer(base.Serializer): + """ + Serializes a QuerySet to XML. + """ + + def start_serialization(self): + """ + Start serialization -- open the XML document and the root element. + """ + self.xml = SimplerXMLGenerator(self.stream, self.options.get("encoding", "utf-8")) + self.xml.startDocument() + self.xml.startElement("django-objects", {"version" : "1.0"}) + + def end_serialization(self): + """ + End serialization -- end the document. + """ + self.xml.endElement("django-objects") + self.xml.endDocument() + + def start_object(self, obj): + """ + Called as each object is handled. + """ + if not hasattr(obj, "_meta"): + raise base.SerializationError("Non-model object (%s) encountered during serialization" % type(obj)) + + self.xml.startElement("object", { + "pk" : str(obj._get_pk_val()), + "model" : str(obj._meta), + }) + + def end_object(self, obj): + """ + Called after handling all fields for an object. + """ + self.xml.endElement("object") + + def handle_field(self, obj, field): + """ + Called to handle each field on an object (except for ForeignKeys and + ManyToManyFields) + """ + self.xml.startElement("field", { + "name" : field.name, + "type" : field.get_internal_type() + }) + + # Get a "string version" of the object's data (this is handled by the + # serializer base class). None is handled specially. + value = self.get_string_value(obj, field) + if value is None: + self.xml.addQuickElement("None") + else: + self.xml.characters(str(value)) + + self.xml.endElement("field") + + def handle_fk_field(self, obj, field): + """ + Called to handle a ForeignKey (we need to treat them slightly + differently from regular fields). + """ + self._start_relational_field(field) + related = getattr(obj, field.name) + if related is not None: + self.xml.characters(str(related._get_pk_val())) + else: + self.xml.addQuickElement("None") + self.xml.endElement("field") + + def handle_m2m_field(self, obj, field): + """ + Called to handle a ManyToManyField. Related objects are only + serialized as references to the object's PK (i.e. the related *data* + is not dumped, just the relation). + """ + self._start_relational_field(field) + for relobj in getattr(obj, field.name).iterator(): + self.xml.addQuickElement("object", attrs={"pk" : str(relobj._get_pk_val())}) + self.xml.endElement("field") + + def _start_relational_field(self, field): + """ + Helper to output the <field> element for relational fields + """ + self.xml.startElement("field", { + "name" : field.name, + "rel" : field.rel.__class__.__name__, + "to" : str(field.rel.to._meta), + }) + +class Deserializer(base.Deserializer): + """ + Deserialize XML. + """ + + def __init__(self, stream_or_string, **options): + super(Deserializer, self).__init__(stream_or_string, **options) + self.encoding = self.options.get("encoding", "utf-8") + self.event_stream = pulldom.parse(self.stream) + + def next(self): + for event, node in self.event_stream: + if event == "START_ELEMENT" and node.nodeName == "object": + self.event_stream.expandNode(node) + return self._handle_object(node) + raise StopIteration + + def _handle_object(self, node): + """ + Convert an <object> node to a DeserializedObject. + """ + # Look up the model using the model loading mechanism. If this fails, bail. + Model = self._get_model_from_node(node, "model") + + # Start building a data dictionary from the object. If the node is + # missing the pk attribute, bail. + pk = node.getAttribute("pk") + if not pk: + raise base.DeserializationError("<object> node is missing the 'pk' attribute") + data = {Model._meta.pk.name : pk} + + # Also start building a dict of m2m data (this is saved as + # {m2m_accessor_attribute : [list_of_related_objects]}) + m2m_data = {} + + # Deseralize each field. + for field_node in node.getElementsByTagName("field"): + # If the field is missing the name attribute, bail (are you + # sensing a pattern here?) + field_name = field_node.getAttribute("name") + if not field_name: + raise base.DeserializationError("<field> node is missing the 'name' attribute") + + # Get the field from the Model. This will raise a + # FieldDoesNotExist if, well, the field doesn't exist, which will + # be propagated correctly. + field = Model._meta.get_field(field_name) + + # As is usually the case, relation fields get the special treatment. + if field.rel and isinstance(field.rel, models.ManyToManyRel): + m2m_data[field.name] = self._handle_m2m_field_node(field_node) + elif field.rel and isinstance(field.rel, models.ManyToOneRel): + data[field.name] = self._handle_fk_field_node(field_node) + else: + value = field.to_python(getInnerText(field_node).strip().encode(self.encoding)) + data[field.name] = value + + # Return a DeserializedObject so that the m2m data has a place to live. + return base.DeserializedObject(Model(**data), m2m_data) + + def _handle_fk_field_node(self, node): + """ + Handle a <field> node for a ForeignKey + """ + # Try to set the foreign key by looking up the foreign related object. + # If it doesn't exist, set the field to None (which might trigger + # validation error, but that's expected). + RelatedModel = self._get_model_from_node(node, "to") + return RelatedModel.objects.get(pk=getInnerText(node).strip().encode(self.encoding)) + + def _handle_m2m_field_node(self, node): + """ + Handle a <field> node for a ManyToManyField + """ + # Load the related model + RelatedModel = self._get_model_from_node(node, "to") + + # Look up all the related objects. Using the in_bulk() lookup ensures + # that missing related objects don't cause an exception + related_ids = [c.getAttribute("pk").encode(self.encoding) for c in node.getElementsByTagName("object")] + return RelatedModel._default_manager.in_bulk(related_ids).values() + + def _get_model_from_node(self, node, attr): + """ + Helper to look up a model from a <object model=...> or a <field + rel=... to=...> node. + """ + model_identifier = node.getAttribute(attr) + if not model_identifier: + raise base.DeserializationError( + "<%s> node is missing the required '%s' attribute" \ + % (node.nodeName, attr)) + try: + Model = models.get_model(*model_identifier.split(".")) + except TypeError: + Model = None + if Model is None: + raise base.DeserializationError( + "<%s> node has invalid model identifier: '%s'" % \ + (node.nodeName, model_identifier)) + return Model + + +def getInnerText(node): + """ + Get all the inner text of a DOM node (recursively). + """ + # inspired by http://mail.python.org/pipermail/xml-sig/2005-March/011022.html + inner_text = [] + for child in node.childNodes: + if child.nodeType == child.TEXT_NODE or child.nodeType == child.CDATA_SECTION_NODE: + inner_text.append(child.data) + elif child.nodeType == child.ELEMENT_NODE: + inner_text.extend(getInnerText(child)) + else: + pass + return "".join(inner_text)
\ No newline at end of file diff --git a/django/db/models/base.py b/django/db/models/base.py index 73abd018ff..09e8eb3fc0 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -399,10 +399,10 @@ def method_set_order(ordered_obj, self, id_list): cursor = connection.cursor() # Example: "UPDATE poll_choices SET _order = %s WHERE poll_id = %s AND id = %s" sql = "UPDATE %s SET %s = %%s WHERE %s = %%s AND %s = %%s" % \ - (backend.quote_name(ordered_obj.db_table), backend.quote_name('_order'), - backend.quote_name(ordered_obj.order_with_respect_to.column), - backend.quote_name(ordered_obj.pk.column)) - rel_val = getattr(self, ordered_obj.order_with_respect_to.rel.field_name) + (backend.quote_name(ordered_obj._meta.db_table), backend.quote_name('_order'), + backend.quote_name(ordered_obj._meta.order_with_respect_to.column), + backend.quote_name(ordered_obj._meta.pk.column)) + rel_val = getattr(self, ordered_obj._meta.order_with_respect_to.rel.field_name) cursor.executemany(sql, [(i, rel_val, j) for i, j in enumerate(id_list)]) transaction.commit_unless_managed() diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index bc6042ae59..8b000d3c2a 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -411,7 +411,7 @@ class DateField(Field): def get_db_prep_lookup(self, lookup_type, value): if lookup_type == 'range': value = [str(v) for v in value] - elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte', 'ne'): + elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte', 'ne') and hasattr(value, 'strftime'): value = value.strftime('%Y-%m-%d') else: value = str(value) diff --git a/django/db/models/loading.py b/django/db/models/loading.py index 10ff3bb8d8..3d34845147 100644 --- a/django/db/models/loading.py +++ b/django/db/models/loading.py @@ -32,18 +32,25 @@ def get_apps(): _app_errors[app_name] = e return _app_list -def get_app(app_label): - "Returns the module containing the models for the given app_label." +def get_app(app_label, emptyOK = False): + "Returns the module containing the models for the given app_label. If the app has no models in it and 'emptyOK' is True, returns None." get_apps() # Run get_apps() to populate the _app_list cache. Slightly hackish. for app_name in settings.INSTALLED_APPS: if app_label == app_name.split('.')[-1]: - return load_app(app_name) + mod = load_app(app_name) + if mod is None: + if emptyOK: + return None + else: + return mod raise ImproperlyConfigured, "App with label %s could not be found" % app_label def load_app(app_name): "Loads the app with the provided fully qualified name, and returns the model module." global _app_list mod = __import__(app_name, '', '', ['models']) + if not hasattr(mod, 'models'): + return None if mod.models not in _app_list: _app_list.append(mod.models) return mod.models diff --git a/django/db/models/manager.py b/django/db/models/manager.py index f679c5492c..46a1710c1c 100644 --- a/django/db/models/manager.py +++ b/django/db/models/manager.py @@ -69,8 +69,11 @@ class Manager(object): def get(self, *args, **kwargs): return self.get_query_set().get(*args, **kwargs) - def get_or_create(self, *args, **kwargs): - return self.get_query_set().get_or_create(*args, **kwargs) + def get_or_create(self, **kwargs): + return self.get_query_set().get_or_create(**kwargs) + + def create(self, **kwargs): + return self.get_query_set().create(**kwargs) def filter(self, *args, **kwargs): return self.get_query_set().filter(*args, **kwargs) diff --git a/django/db/models/query.py b/django/db/models/query.py index e826efa779..efbe68cb66 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -204,6 +204,15 @@ class QuerySet(object): raise self.model.DoesNotExist, "%s matching query does not exist." % self.model._meta.object_name assert len(obj_list) == 1, "get() returned more than one %s -- it returned %s! Lookup parameters were %s" % (self.model._meta.object_name, len(obj_list), kwargs) return obj_list[0] + + def create(self, **kwargs): + """ + Create a new object with the given kwargs, saving it to the database + and returning the created object. + """ + obj = self.model(**kwargs) + obj.save() + return obj def get_or_create(self, **kwargs): """ |
