Is there any downside to Django proxy models? - django

I'm getting rather tired of paging through lots of irrelevant little fiddly properties while looking for the actual database structure of my models. Would it be a bad thing to use proxy models universally just to keep my code better organized / more readable? I.e.
class Foo_Base( models.Model):
title = models.CharField( ...)
# other DB fields. As little as possible anything else.
class Bar_Base( models.Model):
foo = models.ForeignKey( Foo_Base, ... )
etc. not many more lines than there are columns in the DB tables. Then at the bottom or elsewhere,
class Foo( Foo_Base):
class Meta:
proxy=True
#property
def some_pseudo_field(self):
# compute something based on the DB fields in Foo_Base
return result
#property
# etc. pages of etc.
The fact that makemigrations and migrate tracks proxy models makes me slightly concerned, although this usage seems to be exactly what the Django documentation says they are for (wrapping extra functionality around the same database table).
Or is there another way to organize my code that accomplishes the same (keeping fundamental stuff and fiddly little support bits apart).
[Edit] am offering up something that seems to work as a self-answer below. I'd still very much like to hear from anybody who knows for a fact that this is OK, given the deep Django magic on its declarative field declarations.
(About the only thing I dislike about Python, is that it does not have include functionality for reading in a heap of code from another file! )

I think I may have found an answer: use a plugin class inheriting from object,
as is commonplace for class-based Views.
I'd still very much like to hear from anybody who knows for a fact that this is OK, given the deep Django magic on its declarative field declarations.
Minimal proof of concept:
class PenMethods1( object):
#property
def namey(self):
return format_html('<div class="namey">{name}</div>', name=self.name )
class PenTest1(PenMethods1, models.Model):
name = models.CharField( max_length=16, blank=True )
def __repr__(self):
return f'<Pentest1 object id={self.id} name={self.name}>'
Initial migration was OK. Then I added
pnum = models.ForeignKey( 'Pennum', models.SET_NULL, null=True)
(Pennum was something already lying around in my playpen) and ran makemigrations and migrate. Again OK and basic functioning checks out...
>>> from playpen.models import PenTest1, Pennum
>>> n = Pennum.objects.last()
>>> n
<Pennum object id=3 name="t3" num=14 >
>>> p = PenTest1.objects.get(name='bar')
>>> p
<Pentest1 object id=2 name=bar>
>>> p.namey
'<div class="namey">bar</div>'
>>> p.pnum=n
>>> p.save()
>>> n=Pennum.objects.last()
>>> list(n.pentest1_set.all())
[<Pentest1 object id=2 name=bar>]
>>>

Related

Django model choice in separate constant file

According to django docs the best way of handling choice fields in models is do like this:
class Book(models.Model):
AVAILABLE = 'available'
BORROWED = 'borrowed'
ARCHIVED = 'archived'
STATUS = [
(AVAILABLE, 'Available to borrow'),
(BORROWED, 'Borrowed by someone'),
(ARCHIVED, 'Archived - not available anymore'),
]
# […]
status = models.CharField(
max_length=32,
choices=STATUS,
default=AVAILABLE,
)
However, my app grew a lot and model inter-connectivity between different app extended. In some cases I need to import the model class just to use the constant values
eg:
# some other file
from book.models import Book
def do_stuff_if_borrowed(book):
if (book.status == Book.BORROWED):
# do stuff
pass
I'd like to avoid importing models in other file as much as possible because it causes a lot of circular import issues and overall I feel it is not the cleanest way. I checked quickly and didn't find anybody presenting a solution on how to externalize these model constants like:
book/constants.py
AVAILABLE = 'available'
BORROWED = 'borrowed'
ARCHIVED = 'archived'
STATUS = [
(AVAILABLE, 'Available to borrow'),
(BORROWED, 'Borrowed by someone'),
(ARCHIVED, 'Archived - not available anymore'),
]
book/models.py
from .constants import STATUS, AVAILABLE
class Book(models.Model):
status = models.CharField(
max_length=32,
choices=STATUS,
default=AVAILABLE,
)
and then if I need only constants I don't need to imports Book model but only constants.py
Is there a reason not to do like this?
I am working on a project where we are considering something like this. It is connected to another service API which has metrics and definitions that we pull. So we had considered just pulling these files routinely and storing them in something like a metrics.json file and than using them in the models.
This is pretty fluid and nice, but I think what you lose is visibility to changes in models that result in migrations needing to be created, tracked and run.
In your instance, it seems like the constants won't really be changing and the intention is just to be DRY. It seems like you could create that constants file and then add a method the returns prefixed statuses for you model if you did want to have them be more distinct for each model.
What you sacrifice in this instance is easily referencing those values directly from the model. In the example from the docs you could reference the status using Book.STATUS_BORROWED. But in your example, you would have to actually import those constants separately.
I think the best solution is just to make a mixin that covers this and have all of your models that need these statuses inherit from that.
class BorrowableBookMixin(models.Model):
STATUS_AVAILABLE = 'available'
STATUS_BORROWED = 'borrowed'
STATUS_ARCHIVED = 'archived'
STATUS_CHOICES = [
(STATUS_AVAILABLE, 'Available to borrow'),
(STATUS_BORROWED, 'Borrowed by someone'),
(STATUS_ARCHIVED, 'Archived - not available anymore'),
]
status = models.CharField(
max_length=32,
choices=STATUS_CHOICES,
default=STATUS_AVAILABLE,
)
class Meta:
abstract = True

Django, What is the advantage of Modifying a model manager’s initial QuerySet?

The below model have EditorManager,
class EditorManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(role='E')
class Person(models.Model):
first_name = models.CharField(max_length=50)
role = models.CharField(max_length=1, choices=[('A', _('Author')), ('E', _('Editor'))])
people = models.Manager()
editors = EditorManager()
If I query Person.objects.filter(role='E') or Person.editors.all() I gets same result.
then, Why do we go for writing EditorManager() ?
The above code is from Django documentation (https://docs.djangoproject.com/en/3.0/topics/db/managers/).
As mentioned in the Documentation:
using multiple managers on the same model. You can attach as many Manager() instances to a model as you’d like. This is a non-repetitive way to define common “filters” for your models.
Since you just have one action, it may be hard for you to see the benefits. However, as your code gets larger, say:
good = Book.objects.filter(author="PersonA", stars=5).order_by("-date_created").exclude(outdated=True)
normal = Book.objects.filter(author="PersonA", stars=3).order_by("-date_created").exclude(outdated=True)
bad = Book.objects.filter(author="PersonA", stars=1).order_by("-date_created").exclude(outdated=True)
You can see that's an awful lot of code. With managers, you can do something like this:
class AuthorAManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(author="PersonA").order_by("-date_created").exclude(outdated=True)
class Book(models.Model):
# ...
author_a = AuthorAManager()
good = Book.author_a.filter(stars=5)
normal = Book.author_a.filter(stars=3)
bad = Book.author_a.filter(stars=1)
Overall, it can make your code look a lot cleaner and understandable. As you said, you can't see the difference right now as you haven't gone into complex/repeating handles, but as your project expands, I'd say it's a worthwhile investment.

Access to child object in Django relation

Edit: I would very much like to accomplish this without installing a 3rd-party app. It seems simple/common enough that someone would have posted a line of code that accomplishes this by now?
Couldn't this be done easily in SQL? Would it be taboo to just hit the DB with a custom SQL in the index view?
So I have a parent Class and 2 child Classes. I would like to query all items and return a quick list.
from django.db import models
VIDEO_TYPE_CHOICES = (
('dvd', 'DVD'),
('downloaded', 'Downloaded'),
)
BOOK_TYPE_CHOICES = (
('e_book', 'E-Book'),
('print', 'Print'),
('audio', 'Audio Book'),
)
class Unit(models.Model):
name = models.CharField(max_length=200)
image = models.ImageField()
def __unicode__(self):
return self.name
class Video(Unit):
this_type = models.CharField(max_length=20, choices=VIDEO_TYPE_CHOICES, default='dvd')
run_time = models.CharField(max_length=200)
class Book(Unit):
this_type = models.CharField(max_length=20, choices=BOOK_TYPE_CHOICES, default='print')
pages = models.CharField(max_length=200)
All I want to do is display a list of all "Units" with this_type mushed in there on my index page.
Such as:
Lord Of The Rings, lotr.jpeg, DVD
Treasure Island, treasure_island.jpeg, Print
But I only have access to the Units name and image properties if I do a standard "gimme all Units" query...not this_type. Unless of course I make an assumption about the object and try object.book.this_type for example...which throws an exception if that particular object is not a Book.
I've been researching this for a while now...and while I can find several related questions and several possible methods (generic relations, for example?), I cannot find an example that I can relate to my own use case...or understand at all for that matter. I've only been at this stuff (Python and Django) for about a week now...I learn best when I can just make something work, get an understanding of all the moving parts, and then build on that understanding.
In that light, if someone could give me an example of how to generate the previously mentioned object list, I would be extremely grateful!
Pretty PLS???
I would recommend using the django app model_utils
OOP is generally not the best design pattern for models but if you are going to go that route model_utils has an InheritanceManager which does exactly what you want.

Can you explain strange behaviour with Django ManyToManyField?

I have a couple of related models, that look a bit like this:
class Book(models.Model):
title = models.TextField()
class Author(models.Model):
"""
>>> b = Book(title='some title')
>>> b.save()
>>> a = Author(name='some name')
>>> a.save()
>>> a.books.add(b)
>>> b in a.books.all()
True
"""
name = models.TextField()
books = models.ManyToManyField(Book)
This version is a simplification of my production app, but the same test in production fails - a.books.all() returns the empty list, even after I do an a.books.add(b).
I've looked in the database (sqlite), and a new entry has definitely been created in the joining table, book_author. I've also tried calling a transaction.commit(), and a connection.close() to try and refresh the view of the DB. No joy. Any pointers towards what sorts of things might be causing the strange behaviour in production would be gratefully received.
One thought I had was that it had something to do with a third related model, for which I've manually specified a through table, something like this:
class Genre(models.Model):
desc = models.TextField()
books = models.ManyToManyField(Book, through='BookGenres')
class BookGenres(models.Model):
book = models.ForeignKey(Book)
genre = models.ForeignKey(Genre)
However, adding this to the test app doesn't break things... What else should I look for?
[edit 11/5] more weird behaviour, following on from advice from Daniel the comments (thanks for trying! :-)
More strange behaviour:
>>>a.books.all()
[]
>>>a.books.filter(pk=b.id)
[]
>>>a.books.filter(pk=b.id).count()
1
>>>len(a.books.filter(pk=b.id))
0
As I say, my "real" models are more complex, and I haven't been able to replicate this behaviour in simplified tests, but any ideas for what to look at would be gratefully appreciated.
I'm not sure that the in operator necessarily works in querysets. Don't forget that Django model instances don't have identity, so two objects loaded from the db in two separate operations can have different internal IDs even if they have the same pk.
I would explicitly query the item you're expecting :
>>> a.books.add(b)
>>> a.books.filter(pk=b.pk).count()
1
I'd also add that I don't see the point of this test. Django's model operations are very well covered by its own test suite - you should reserve your unit/doctests for your own business logic.

Django - optimization question

If you have some models:
class Teacher(models.Model):
name = models.CharField(max_length=50)
class Student(models.Model):
age = models.PositiveIntegerField()
teacher = models.ForeignKey(Teacher, related_name='students')
and you use it like this:
>>> student = Student.objects.get(pk=1)
>>> student.teacher.name # This hits the database
'Some Teacher'
>>> student.teacher.name # This doesn't (``teacher`` is cached on the object)
'Some Teacher'
That's awesome. Django caches the related object so that you can use it again without having to abuse your database.
But, if you use it like this:
>>> teacher = Teacher.objects.get(pk=1)
>>> for student in teacher.students.all(): # This hits the database
... print(student.age)
...
8
6
>>> for student in teacher.students.all(): # This does too (obviously)
... print(student.age)
...
8
6
There's no caching or efficient access to related objects this direction.
My question is thus: Is there a built-in (or non-problematic way) to backward access related objects in an efficient way (a cached way), like you can in the student.teacher example above?
The reason I want this is because I have a model with multiple methods that need access to the same related objects over and over, so a page that should have 12 queries ends up with about 30.
There isn't any built-in way. I've written about this issue on my blog, with a technique to optimise accessing reverse relationships.
orokusaki,
You just need to cache the queryset as a python variable
students = teacher.students.all()
And then just use students in your for loops.
Below is a link to Django's own documentation about this specific issue :-)
http://docs.djangoproject.com/en/1.1/topics/db/optimization/#understand-cached-attributes
Try this perhaps?
teacher = Teacher.objects.select_related().get(pk=1)
http://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.QuerySet.select_related
I have never used select_related in concert with a .all() on its result, so I'm not sure if it will yield DB savings or not.