Get variable OneToOneField in Django - python

Lets say I have a Form model:
class Form(models.Model):
name = models.TextField()
date = models.DateField()
and various "child" models
class FormA(models.Model):
form = models.OneToOneField(Form, on_delete=models.CASCADE)
property_a = models.TextField()
class FormB(models.Model):
form = models.OneToOneField(Form, on_delete=models.CASCADE)
property_b = models.IntegerField()
class FormC(models.Model):
form = models.OneToOneField(Form, on_delete=models.CASCADE)
property_c = models.BooleanField()
a Form can be one AND ONLY ONE of 3 types of forms (FormA, FormB, FormC). Given a Query Set of Form, is there any way I can recover what types of Form (A, B or C) they are?

I would need to get a better understanding of your actual use case to know whether this is a good option for you or not, but in these situations, I would first suggest using model inheritance instead of a one to one field. The code you have there is basically doing what multi-table inheritance already does.
Take a read through the inheritance docs real quick first and make sure that multi-table inheritance makes sense for you as compared to the other options provided by django. If you do wish to continue with multi-table inheritance, I would suggest taking a look at InheritanceManager from django-module-utils.
At this point (if using InheritanceManager), you would be able to use isinstance.
for form in Form.objects.select_subclasses():
if isinstance(form, FormA):
..... do stuff ......
This might sound like a lot of extra effort but IMO it would reduce the moving parts (and custom code) and make things easier to deal with while still handling the functionality you need.

You can check it by name or isinstance.
a = FormA()
print(a.__class__)
print(a.__class__.__name__)
print(isinstance(a, Forma))
outputs:
<class __main__.FormA at 0xsomeaddress>
'FormA'
True
------------------- EDIT -----------------
Ok based on your comment, you just want to know which instance is assigned to your main Form.
So you can do something like this:
if hasattr(form, 'forma'):
# do something
elif hasattr(form, 'formb'):
# do something else
elif hasattr(form, 'formb'):
# do something else

After investigating a bit I came up with this
for form in forms:
#reduces fields to those of OneToOne types
one_to_ones = [field for field in form._meta.get_fields() if field.one_to_one]
for o in one_to_ones:
if hasattr(form,o.name):
#do something
Might have some drawbacks (maybe bad runtime) but serves its purpose for now.
Ideas to improve this are appreciated

Related

Django 'likes' - ManyToManyField vs new model

I'm implementing likes on profiles for my website and I'm not sure which would be the best practice, a ManyToManyField like so:
class MyUser(AbstractBaseUser):
...
likes = models.ManyToManyField('self', symmetrical = False, null = True)
...
or just creating a class Like, like so:
class Like(models.Model):
liker = models.ForeignKey(MyUser, related_name='liker')
liked = models.ForeignKey(MyUser, related_name='liked')
Is one of them a better choice than the other? If so, why?
thanks
The first option should be preffered. If you need some additional fields to describe the likes, you can still use through="Likes" in your ManyToManyField and define the model Likes.
Manipulating the data entries would be also somewhat more pythonic:
# returns an object collection
likes_for_me = MyUser.objects.filter(pk=1).likes
instead of:
me = MyUser.objects.filter(pk=1)
likes_for_me = Like.objects.filter(liked=me)
The second option is basically what is done internally: a new table is created, which is used to create the links between the entities.
For the first option, you let django do the job for you.
The choice is certainly more about how you want to do the requests. On the second options, you would have to query the Like models that match you model, while on the first one, you only have to request the MyUser, from which you can access the connections.
Second option is more flexible and extensible. For example, you'll probably want to track when like was created (just add Like.date_created field). Also you'll probably want to send notification to content author when content was liked. But at first like only (add Like.cancelled boolead field and wrap it with some logic...).
So I'll go with separate model.
I think the one you choose totally depends on the one you find easier to implement or better. I tend to always use the first approach, as it is more straightforward and logical, at least to me. I also disagree with Igor on that it's not flexible and extensible, you can also initiate notifications when it happens. If you are going to use the Django rest framework, then I totally suggest using the first method, as the second could be a pain.
class Post(models.Model):
like = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, related_name='post_like')
Then in your view, you just do this.
#api_view(['GET'])
#permission_classes([IsAuthenticated])
def like(request, id):
signed_in = request.user
post = Post.objects.get(id=id)
if signed_in and post:
post.like.add(signed_in)
# For unlike, remove instead of add
return Response("Successful")
else:
return Response("Unsuccessful", status.HTTP_404_NOT_FOUND)
Then you can use the response however you like on the front end.

Tastypie: include computed field from a related model?

I've looked through Tastypie's documentation and searched for a while, but can't seem to find an answer to this.
Let's say that we've got two models: Student and Assignment, with a one-to-many relationship between them. The Assignment model includes an assignment_date field. Basically, I'd like to build an API using Tastypie that returns Student objects sorted by most recent assignment date. Whether the sorting is done on the server or in the client side doesn't matter - but wherever the sorting is done, the assignment_date is needed to sort by.
Idea #1: just return the assignments along with the students.
class StudentResource(ModelResource):
assignments = fields.OneToManyField(
AssignmentResource, 'assignments', full=True)
class Meta:
queryset = models.Student.objects.all()
resource_name = 'student'
Unfortunately, each student may have tens or hundreds of assignments, so this is bloated and unnecessary.
Idea #2: augment the data during the dehydrate cycle.
class StudentResource(ModelResource):
class Meta:
queryset = models.Student.objects.all()
resource_name = 'student'
def dehydrate(self, bundle):
bundle.data['last_assignment_date'] = (models.Assignment
.filter(student=bundle.data['id'])
.order_by('assignment_date')[0].assignment_date)
This is not ideal, since it'll be performing a separate database roundtrip for each student record. It's also not very declarative, nor elegant.
So, is there a good way to get this kind of functionality with Tastypie? Or is there a better way to do what I'm trying to achieve?
You can sort a ModelResource by a field name. Check out this part of the documentation http://django-tastypie.readthedocs.org/en/latest/resources.html#ordering
You could also set this ordering by default in the Model: https://docs.djangoproject.com/en/dev/ref/models/options/#ordering

Best practice for accessing distantly related Django models

Let say we have a long chain of Django models, where each references the one above through a ForeignKey field:
class One(models.Model):
# fields
class Two(models.Model):
one = models.ForeignKey(One)
...
class Ten(models.Model):
nine = models.ForeignKey(Nine)
Good! Now image, if you will, having an instance of the Ten model and wanting to grab the related One instance. This can result in long lines of attribute chaining like this:
ten_instance.nine.eight.seven.six.five.four.three.two.one
I'm wondering what the standard approach would be to this niggling issue. Do we leave it as is, being inherently descriptive and readable. Or do we aim to shorten such a line to make things more simple:
ten_instance.one
 But What's The Best Practice Here? Or is there a more simple solution?
Use Properties
My current approach would be to add a property to the Ten model, abstracting away that attribute chaining:
class Ten(models.Model):
nine = models.ForeignKey(Nine)
#property
def one(self):
return self.nine.eight.seven.six.five.four.three.two.one
I can see a downside to this tactic however, and that's the added mysticism involved. Does the Ten instance actually have a relation to the One model or not? I wouldn't be able to tell without inspecting the model myself.
You probably want to use django-mptt for sophisticated hierarchal models although it can be a bit ott. If you want a simple hierarchy then add a ForeignKey to self:
class Number(models.Model):
parent = models.ForeignKey('self', blank=True, null=True,
related_name='child')
then the query would be something like this based on a unique field, say slug:
Number.objects.get(parent__slug='one')

quick question: clear an attribute of a model in django

i think this is a pretty easy question for you.
I want to clear an attribute of a django-model.
If i have something like this:
class Book(models.Model):
name = models.TextField()
pages = models.IntegerField()
img = models.ImageField()
In an abstract function i want to clear an attribute, but at that time i don't know what type the field has. So examples would be name="", pages=0 or img=None.. Is there a way to do it in a generic way? I search something like SET_TO_EMPTY(book,"pages")
Do you know a function like that?
many thanks in advance!
I don't think there is a clean way of doing it. However, assuming you've set a default value (which you don't have) you can do it like this:
book.img = book._meta.get_field('img').default
Do note that your current model won't allow a None value. To allow those you have to set null=True and blank=True. For pages you need a default=0.

Django labels and translations - Model Design

Lets say I have the following Django model:
class StandardLabel(models.Model):
id = models.AutoField(primary_key=True)
label = models.CharField(max_length=255)
abbreviation = models.CharField(max_length=255)
Each label has an ID number, the label text, and an abbreviation. Now, I want to have these labels translatable into other languages. What is the best way to do this?
As I see it, I have a few options:
1: Add the translations as fields on the model:
class StandardLabel(models.Model):
id = models.AutoField(primary_key=True)
label_english = models.CharField(max_length=255)
abbreviation_english = models.CharField(max_length=255)
label_spanish = models.CharField(max_length=255)
abbreviation_spanish = models.CharField(max_length=255)
This is obviously not ideal - adding languages requires editing the model, the correct field name depends on the language.
2: Add the language as a foreign key:
class StandardLabel(models.Model):
id = models.AutoField(primary_key=True)
label = models.CharField(max_length=255)
abbreviation = models.CharField(max_length=255)
language = models.ForeignKey('languages.Language')
This is much better, now I can ask for all labels with a certain language, and throw them into a dict:
labels = StandardLabel.objects.filter(language=1)
labels = dict((x.pk, x) for x in labels)
But the problem here is that the labels dict is meant to be a lookup table, like so:
x = OtherObjectWithAReferenceToTheseLabels.object.get(pk=3)
thelabel = labels[x.labelIdNumber].label
Which doesn't work if there is a row per label, possibly with multiple languages for a single label. To solve that one, I need another field:
class StandardLabel(models.Model):
id = models.AutoField(primary_key=True)
group_id = models.IntegerField(db_index=True)
label = models.CharField(max_length=255)
abbreviation = models.CharField(max_length=255)
language = models.ForeignKey('languages.Language')
class Meta:
unique_together=(("group_id", "language"),)
#and I need to group them differently:
labels = StandardLabel.objects.filter(language=1)
labels = dict((x.group_id, x) for x in labels)
3: Throw label text out into a new model:
class StandardLabel(models.Model):
id = models.AutoField(primary_key=True)
text = models.ManyToManyField('LabelText')
class LabelText(models.Model):
id = models.AutoField(primary_key=True)
label = models.CharField(max_length=255)
abbreviation = models.CharField(max_length=255)
language = models.ForeignKey('languages.Language')
labels = StandardLabel.objects.filter(text__language=1)
labels = dict((x.pk, x) for x in labels)
But then this doesn't work, and causes a database hit every time I reference the label's text:
x = OtherObjectWithAReferenceToTheseLabels.object.get(pk=3)
thelabel = labels[x.labelIdNumber].text.get(language=1)
I've implemented option 2, but I find it very ugly - i don't like the group_id field, and I can't think of anything better to name it. In addition, StandardLabel as i'm using it is an abstract model, which I subclass to get different label sets for different fields.
I suppose that if option 3 /didn't/ hit the database, it's what I'd choose. I believe the real problem is that the filter text__language=1 doesn't cache the LabelText instances, and so the DB is hit when I text.get(language=1)
What are your thoughts on this? Can anyone recommend a cleaner solution?
Edit: Just to make it clear, these are not form labels, so the Django Internationalization system doesn't help.
Another option you might consider, depending on your application design of course, is to make use of Django's internationalization features. The approach they use is quite common to the approach found in desktop software.
I see the question was edited to add a reference to Django internationalization, so you do know about it, but the intl features in Django apply to much more than just Forms; it touchs quite a lot, and needs only a few tweaks to your app design.
Their docs are here: http://docs.djangoproject.com/en/dev/topics/i18n/#topics-i18n
The idea is that you define your model as if there was only one language. In other words, make no reference to language at all, and put only, say, English in the model.
So:
class StandardLabel(models.Model):
abbreviation = models.CharField(max_length=255)
label = models.CharField(max_length=255)
I know this looks like you've totally thrown out the language issue, but you've actually just relocated it. Instead of the language being in your data model, you've pushed it to the view.
The django internationalization features allow you to generate text translation files, and provides a number of features for pulling text out of the system into files. This is actually quite useful because it allows you to send plain files to your translator, which makes their job easier. Adding a new language is as easy as getting the file translated into a new language.
The translation files define the label from the database, and a translation for that language. There are functions for handling the language translation dynamically at run time for models, admin views, javascript, and templates.
For example, in a template, you might do something like:
<b>Hello {% trans "Here's the string in english" %}</b>
Or in view code, you could do:
# See docs on setting language, or getting Django to auto-set language
s = StandardLabel.objects.get(id=1)
lang_specific_label = ugettext(s.label)
Of course, if your app is all about entering new languages on the fly, then this approach may not work for you. Still, have a look at the Internationalization project as you may either be able to use it "as is", or be inspired to a django-appropriate solution that does work for your domain.
I would keep things as simple as possible. The lookup will be faster and the code cleaner with something like this:
class StandardLabel(models.Model):
abbreviation = models.CharField(max_length=255)
label = models.CharField(max_length=255)
language = models.CharField(max_length=2)
# or, alternately, specify language as a foreign key:
#language = models.ForeignKey(Language)
class Meta:
unique_together = ('language', 'abbreviation')
Then query based on abbreviation and language:
l = StandardLabel.objects.get(language='en', abbreviation='suite')
I'd much prefer to add a field per language than a new model instance per language. It does require schema alteration when you add a new language, but that isn't hard, and how often do you expect to add languages? In the meantime, it'll give you better database performance (no added joins or indexes) and you don't have to muck up your query logic with translation stuff; keep it all in the templates where it belongs.
Even better, use a reusable app like django-transmeta or django-modeltranslation that makes this stupid simple and almost completely transparent.
Although I would go with Daniel's solution, here is an alternative from what I've understood from your comments:
You can use an XMLField or JSONField to store your language/translation pairs. This would allow your objects referencing your labels to use a single id for all translations. And then you can have a custom manager method to call a specific translation:
Label.objects.get_by_language('ru', **kwargs)
Or a slightly cleaner and slightly more complicated solution that plays well with admin would be to denormalize the XMLField to another model with many-to-one relationship to the Label model. Same API, but instead of parsing XML it could query related models.
For both suggestions there's a single object where users of a label will point to.
I wouldn't worry about the queries too much, Django caches queries and your DBMS would probably have superior caching there as well.

Categories