Alternative code position for Django admin list display settings - python

I am following the tutorial of Django 1.6, which includes a model Poll that has a derived attribute was_published_recently (a method of class Poll). The model was originally defined as follows.
# polls/models.py (excerpted)
class Poll(models.Model):
# ...
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
And the admin interface for this app:
# polls/admin.py (excerpted)
class PollAdmin(admin.ModelAdmin):
# ...
list_display = ('question', 'pub_date', 'was_published_recently')
admin.site.register(Poll, PollAdmin)
Now we want to improve the display and sorting functionality of was_published_recently.
In the tutorial, file polls/models.py is updated:
class Poll(models.Model):
# ...
was_published_recently.admin_order_field = 'pub_date'
was_published_recently.boolean = True
was_published_recently.short_description = 'Published recently?'
However, I think this may not be good enough in practice, because what we are specifying is completely about the admin user interface, not the model per se. So I instead updated polls/admin.py:
class PollAdmin(admin.ModelAdmin):
Poll.was_published_recently.admin_order_field = 'pub_date'
Poll.was_published_recently.boolean = True
Poll.was_published_recently.short_description = 'Published recently?'
# ...
After this modification, the app works as expected as well (multiple polls work well also). Since I am new to Python, I investigated a bit further by printing was_published_recently in both Poll and PollAdmin:
class Poll(models.Model):
# ...
print("in class Poll", was_published_recently)
class PollAdmin(admin.ModelAdmin):
print("in class PollAdmin", Poll.was_published_recently)
# ...
And the output is
in class Poll <function Poll.was_published_recently at 0x10fc92050>
in class PollAdmin <function Poll.was_published_recently at 0x10fc92050>
So apparently was_published_recently in class Poll is the same as Poll.published_recently accessed in class PollAdmin.
My question: Should I specify the admin_order_field stuffs in admin.py or models.py? If specified in admin.py instead of models.py, are there any drawbacks?

Briefly, everything is working as intended, and it's semantically the same to put admin_order_field stuffs either in models.py or admin.py.
So apparently was_published_recently in class Poll is the same as Poll.published_recently accessed in class PollAdmin.
Yes. They should be the same. Classes are objects. So Poll is an object. Poll.was_published_recently is an attribute of object Poll, and is also an object with type instancemethod. Wherever you access it, it is the same object.
AFAIK, the only difference between writing admin_order_field stuffs in models.py and admin.py is the order of interpretation. Django loads models.py before admin.py, so everything runs at loading time and relies on admin_order_field stuffs will fail. But since admin_order_field is only used in admin.py, I think what you are doing is fine.
Python is so dynamic that allows you to modify definitions from even other modules. This can lead to hard-to-find bugs and bad readability / maintainability. Be sure to use this black magic with caution.

Related

How and why I can't override related manager method on django?

I have this manager:
class ConfigValueManager(models.Manager):
def get(self, key):
config_value = self.filter(key=key).first()
if config_value:
type_caster = locate(config_value.type)
return type_caster(config_value.value)
return config_value
def set(self, key, value):
self.filter(key=key).update(value=value)
def set2(self, key, value):
qs = self.filter(key=key)
if qs:
qs.update(value=value, type=type(value).__name__, company=self.instance)
else:
self.create(key=key, value=value, type=type(value).__name__, company=self.instance)
the problem is that I can't overwrite set. The method is still coming from the parent, even though I've created set on the child. Funny thing is that get and set2 are fine. Even add which isn't in my example can't be overridden.
My question is how can I overwrite set and why this happens?
I add some details on why it's not easily possible, because I struggled on the same issue.
set, like add or create, are overridden in the dynamically created RelatedManager, as we can see in the django source code. This RelatedManager actually uses our manager as a super class that's why your get and set2 methods can used, but it does not help for overridden methods.
This manager is created in the ReverseManyToOneDescriptor.related_manager_cls cached property. In the example on your github snippet, Company.config_values is an instance of this ReverseManyToOneDescriptor.
I'll show an example on how to override the set method, by making some assumptions on your code, because it misses some definitions (like the Company model, the ForeignKey field inside FooConfigValue.)
I don't advise to use it, as it's absolutely not robust against django changes, and I didn't do any test, it just serves as a proof on how RelatedManager instances are created
Add this at the end of the example code and it should work
def modify_related_manager_set(model_cls):
# model_cls = Company here, and config_values is the related field name
reverse_descriptor = model_cls.config_values
base_set = reverse_descriptor.related_manager_cls.set
def custom_set(*args, **kwargs):
print("in my custom set")
return base_set(*args, **kwargs)
reverse_descriptor.related_manager_cls.set = custom_set
# do this call after all the models have been created
# e.g. after defining FooConfigValue
modify_related_manager_set(Company)
And you should now see the in my custom set being printed.
I know this doesn't help much, but at least it helped understand how related fields work
models.py
from django.db import models
from django.db.models.query import QuerySet
class PersonQuerySet(QuerySet):
def set(self, slug, **kwargs):
return self.filter(slug=slug).update(**kwargs)
class Person(models.Model):
name = models.CharField(max_length=100, null=True)
slug = models.CharField(max_length=10, null=True)
objects = PersonQuerySet.as_manager()
tests.py
from django.test import TestCase
from core.models import Person
class TestSet(TestCase):
def test_just_update_records_with_the_same_slug(self):
Person.objects.create(slug='batman', name='John')
Person.objects.create(slug='batman', name='Connor')
Person.objects.create(slug='bruce', name='Ill be back')
Person.objects.set('batman', name='###')
expected_value = 2
result = Person.objects.filter(name='###').count()
self.assertEqual(result, expected_value)
github example
https://github.com/luivilella/django-test-manager

Django Rest Framework dynamic nested serialization

I have a model, Parent which contains a Django ContentType GenericForeignKey relationship to various child models (ChildA, ChildB) in Parent.child
I'm trying to get ListCreateAPIView and other listing views working with this setup. Originally I handled the serialization of the child instance using a SerializerMethodField which looked something like:
class ParentSerializer(serializers.ModelSerializer):
child = serializers.SerializerMethodField('get_child')
(other fields)
def get_child(self, obj):
if obj.content_type == "child_a":
return ChildASerializer(obj.child).data
...
Now I want to take advantage of Django Rest Framework to its full (including deserialization/creation/validation) so I want to avoid my current approach and increase DRYness by doing:
class ParentSerializer(serializers.ModelSerializer):
(code for serializing parent fields except 'child' attribute)
class ChildASerializer(serializers.ModelSerializer):
(code for ChildA fields)
class ParentTypeASerializer(ParentSerializer):
child = ChildASerializer()
If i'm reading the docs right, this means POST/PUT will go through the serializer's process without me having to override the post methods in views and other uglyness. This is important as ChildA,ChildB,ChildC come from plugins and the core Parent/ParentSerializer should be as unaware of them as possible.
My thinking was to override get_serializer() in the view, but when listing many objects, I don't see how I can provide ParentTypeASerializer, ParentTypeBSerializer etc in the view.
def get_serializer(self, instance=None, data=None, files=None, many=False, partial=True):
serializer_class = None
if instance and instance.content_type == "child_a":
serializer_class = ParentTypeASerializer
if instance and instance.content_type == "child_b":
serializer_class = ParentTypeBSerializer
...
# What about many=True ?!
return serializer_class(instance,data=data,files=files,many-many,partial=partial,context=context)
Another idea I had was to write a PolymorphicField class extending WritableField that does the decision. Unsure if this is the simplest approach:
class ParentSerializer(serializers.ModelSerializer):
child = PolymorphicChildSerializerProxy() # Passes through/wraps the right serializer
question: is there any dynamic/runtime/per-object way to provide the right serializer for an Generic/polymorphic nested object in such a way either at the view level or the parent serializer? Ideally something like the second example and an override in the view that works for List/Create/Destroy generics or like the first example except I return a serializer class rather than serialized data?

PyCharm doesn't understand custom Manager of model

I extend default model manager and add cache-specific logic to it:
class ReadOnlyManager(manager.Manager):
use_for_related_fields = True
def create(self, **kwargs):
obj = super(ReadOnlyManager, self).create(**kwargs)
cache.cache_read_only_object(obj)
...
return obj
def update(self, *args, **kwargs):
raise ReadOnlyException()
def by_id(self, object_id):
return cache.retrieve_read_only_object(self.model, object_id)
def by_lookup(self, lookup_key, lookup_value):
return cache.retrieve_read_only_object_by_lookup(self.model, lookup_key, lookup_value)
Then I created abstract model that uses it:
class ReadOnlyModel(models.Model):
class Meta:
abstract = True
objects = ReadOnlyManager()
I use it in concrete model:
class TokenType(ReadOnlyModel):
code = models.CharField(_('code'), max_length=30, unique=True)
description = models.CharField(_('description'), max_length=100)
lookups = {
'code': 'code'
}
When I tried to call method specific for custom cache, for example *by_id*:
TokenType.objects.by_id(1) # This code works
PyCharm highlights it and writes "Unresolved attribute reference" warning. When I press CMD+Space after TokenType., I see, that autocomplete box contains two objects items: one marked with function icon and have type ReadOnlyManager, second - with method icon and have type Manager.
Is it PyCharm bug? How to enable autocomlete for additional methods in custom manager?
This seems to be a problem of PyCharm. Writing an auto completion for Python is really a hard task, especially for things like Django Models, which uses Meta Classes and other nasty stuff.
However it is possible to complete, and it seems not so difficult, for your example my autocompletion ( https://github.com/davidhalter/jedi/tree/dev, work in progress, don't use it yet) is able to complete it:
Completing TokenType.objects. :
update
by_id
by_lookup
create
use_for_related_fields
Completing TokenType.:
__metaclass__
__hash__
_get_next_or_previous_in_order
__ne__
date_error_message
description
_perform_date_checks
delete
clean
objects
unique_error_message
_set_pk_val
_deferred
save_base
pk
serializable_value
full_clean
__init__
code
save
__str__
validate_unique
clean_fields
__repr__
_perform_unique_checks
__reduce__
_get_unique_checks
prepare_database_save
_get_pk_val
__eq__
lookups
_get_next_or_previous_by_FIELD
Meta
_get_FIELD_display
As far as I'm concerned, PyCharm is Closed Source, so I think you'll have to talk to the PyCharm developers.

Why are form field __init__ methods being called on Django startup?

I have a Django app with custom form fields, some of which have slow operations in their constructors. I was surprised recently to find out that those constructors were getting called when Django itself was starting up, even before a user does something that requires that form in a view.
Why are they getting instantiated at server start?
Example:
urls.py:
from myapp.views import view1
...
url(r'^test$', view1.test),
views/view1.py:
class MyForm(ModelForm):
class Meta:
model = MyModel
field1 = MyChoiceField()
class MyChoiceField(ChoiceField):
def __init__(self, choices=(), required=True, widget=None, label=None,
initial=None, help_text=None, *args, **kwargs):
super(ChoiceField, self).__init__(required, widget, label, initial,
help_text, *args, **kwargs)
self.choices = [(m.id, m.name) for m in ReallyLargeTableModel.objects.all()]
If I set a break point inside that field constructor, then start up Django, it breaks the first time I request any page, even if the view in question does not need that form or field. The stacktrace leads back to the import line in urls.py.
Is this because I'm importing view1 in urls.py, instead of importing view1.test?
Edit: This isn't Django specific, here is a test case the illustrates the behavior:
class Something():
def __init__(self):
print "Something __init__() called"
class UsesSomething():
field = Something()
If you run this in the interactive terminal, it will print "Something init() called". This was surprising to me because I have not actually instantiated a UsesSomething object.
Because you instantiate the fields in the form definition, which is presumably being imported by one of your views.
The field init is the wrong place to do this sort of dynamic initialization, for this exact reason. You want something that is called when the form is initialized: ie, the form's __init__.
That said, you don't actually want to do this at all - you just need to use forms.ModelChoiceField, which takes a queryset and does the dynamic assignment of choices for you.
class MyForm(ModelForm):
field1 = forms.ModelChoiceField(queryset=ReallyLargeTableModel.objects.all())
In your example:
class UsesSomething():
field = Something()
The line of code field = Something() will execute when you import the containing module as Python processes the class definition. This is just how Python works. You can actually put arbitrary code inside a class definition.
module: test.py:
class UsesSomething():
print "wow!"
>>> import test
wow!

Add django model manager code-completion to Komodo

I have been using ActiveState Komodo for a while and while most of the code-completion is spot on it lacks the code completion from Django's model manager.
I have included the Django directory in my PYTHONPATH and get most of the code completion, the notable exception being the models.
Assuming I have a model users I would expect the code users.objects. to show autocomplete options such as all(),count(),filter() etc. however these are added by the model's manager which does so in a seemingly abnormal way.
I am wondering if I can 'force' Komodo to pick up the models.
The model manager looks to be included from the following code (taken from manager.py)
def ensure_default_manager(sender, **kwargs):
"""
Ensures that a Model subclass contains a default manager and sets the
_default_manager attribute on the class. Also sets up the _base_manager
points to a plain Manager instance (which could be the same as
_default_manager if it's not a subclass of Manager).
"""
cls = sender
if cls._meta.abstract:
return
if not getattr(cls, '_default_manager', None):
# Create the default manager, if needed.
try:
cls._meta.get_field('objects')
raise ValueError("Model %s must specify a custom Manager, because it has a field named 'objects'" % cls.__name__)
except FieldDoesNotExist:
pass
cls.add_to_class('objects', Manager())
cls._base_manager = cls.objects
...
Specifically the last two lines. Is there any way to tell Komodo that <model>.objects = Manager() so the proper code completion is shown?
Probably the easiest way to get this to work seems to be to add the following to the top of models.py:
from django.db.models import manager
and then under each model add
objects = manager.Manager()
so that, for example, the following:
class Site(models.Model):
name = models.CharField(max_length=200)
prefix = models.CharField(max_length=1)
secret = models.CharField(max_length=255)
def __unicode__(self):
return self.name
becomes
class Site(models.Model):
name = models.CharField(max_length=200)
prefix = models.CharField(max_length=1)
secret = models.CharField(max_length=255)
objects = manager.Manager()
def __unicode__(self):
return self.name
This is how you would (explicitly) set your own model manager, and by explicitly setting the model manager (to the default) Kommodo picks up the code completion perfectly.
Hopefully this will help someone :-)

Categories

Resources