Django labels and translations - Model Design - python

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.

Related

Django customize ForeignKey way of instances returnment

I have a need to create custom field, that is very similar to a ForeignKey field but has specific logic. Two main tasks are:
Each related model of this CustomForeignKey has regular ForeignKey field to the model itself and my purpose is to return one of this instances depending on some parameter (date for example). Maybe it would be more clear with some example:
class Author(models.Model):
name = models.CharField(max_length = 30)
surname = models.CharField(max_length = 60)
publication = CustomForeignKey(Publication)
class Publication(models.Model):
title = models.CharField(max_length = 50)
text = models.TextField()
date = models.DateTimeField()
source = models.ForeigKey('self', related_name='references')
I want calls to SomeAuthor.publication be performed like SomePublication.references.order_by('date').first(). Real goal is more difficult, but the sense is nearly the same.
Second thing is about lookups. When I filter objects by this CustomForeignKey, I would like to have same logic as in previous point. So Author.objects.filter(publication__title = 'Some Title') should make filtering by the fist object ordered by date from references related manager.
I read the documentation about creating custom fields in django, but there are no good examples about custom relational fields. In django.db.models.fields.related as well, I didn't find which methods should I redefine to achieve my goal. There are to_python and from_db_value, but they are used only in regular fields, not related ones.
Maybe someone had more experince with custom relational fields, so I would be tankful for any advice!

ForeignKey vs CharField

I have an idea for data model in django and I was wondering if someone can point out pros and cons for these two setups.
Setup 1: This would be an obvious one. Using CharFields for each field of each object
class Person(models.Model):
name = models.CharField(max_length=255)
surname = models.CharField(max_length=255)
city = models.CharField(max_length=255)
Setup 2: This is the one I am thinking about. Using a ForeignKey to Objects that contain the values that current Object should have.
class Person(models.Model):
name = models.ForeignKey('Name')
surname = models.ForeignKey('Surname')
city = models.ForeignKey('City')
class Chars(models.Model):
value = models.CharField(max_length=255)
def __str__(self):
return self.value
class Meta:
abstract = True
class Name(Chars):pass
class Surname(Chars):pass
class City(Chars):pass
So in setup 1, I would create an Object with:
Person.objects.create(name='Name', surname='Surname', city='City')
and each object would have it's own data. In setup 2, I would have to do this:
_name = Name.objects.get_or_create(value='Name')[0]
_surname = Surname.objects.get_or_create(value='Surname')[0]
_city = City.objects.get_or_create(value='City')[0]
Person.objects.create(name=_name, surname=_surname, city=_city)
Question: Main purpose for this would be to reuse existing values for multiple objects, but is this something worth doing, when you take into consideration that you need multiple hits on the database to create an Object?
Choosing the correct design pattern for your application is a very wide area which is influenced by many factors that are even possibly out of scope in a Stack Overflow question. So in a sense your question could be a bit subjective and too broad.
Nevertheless, I would say that assigning a separate model (class) for first name, another separate for last name etc. is an overkill. You might essentially end up overengineering your app.
The main reasoning behind the above recommendation is that you probably do not want to treat a name as a separate entity and possibly attach additional properties to it. Unless you really would need such a feature, a name is usually a plain string that some users happen to have identical.
It doesn't make any good to keep name and surname as separate object/model/db table. In your setup, if you don't set name and surname as unique, then it doesn't make any sense to put them in separate model. Even worse, it will incur additional DB work and decrease performance. Now, if you set them as unique, then you have to work over the situation when, e.g. some user changes his name and by default it would be changed for all users with that name.
On the other hand, city - there're not that many cities and it's a good idea to keep it as separate object and refer to it via foreign key from user. This will save disk space, allow to easily get all users from same city. Even better, you can prepopulate cities DB and provide autocompletion fro users entering there city. Though for performance you might still want to keep city as a string on the user model.
Also, to mention 'gender' field, since there're not many possible choices for this data, it's worth to use enumeration in your code and store a value in DB, i.e. use choices instead of ForeignKey to a separate DB table.

Django - column contains list of objects

I'm looking for a way to store rows from another table in a table. I have a model called Languages and model called Freelancers. The thing I want is to store which languages the freelancer work with.
I think that if there were relationship Many to One, there would be just language = models.ForeignKey(Languages). The problem is that the one freelancer could know more than one languages.
Here are those models:
class Languages(models.Model):
language = models.CharField(max_length=100)
language_shortcut = models.CharField(max_length=3)
price_per_word = models.FloatField()
class Freelancers(models.Model):
nickname = models.CharField(max_length=30)
# languages = models.ForeignKey(Languages) # This is incorrect
# languages = list(models.ForeignKey....) # I'm looking for something like this...
Since I'm new to Django I appreciate any advices.
This is the exact use case for a many-to-many relationship, which in Django is expressed by a ManyToManyField:
languages = models.ManyToManyField(Languages)
Now for each freelancer you can do freelancer.languages.all() and for each language you can do language.freelancers_set.all().
Note that in Django it is usual to use singular names for models: Freelancer and Language - because each instance refers to a single one.

Database structure in Django for voting app

I'm trying to wrap my head around how I would structure my database tables in the Django webapp I'm writing. I'm a relative newbie to web development, but this is the very first time I've tried to use a database, so bear with me if it's a stupid question.
The webapp goes through each Oscar the Academy gives out and allows the user to select which of some (varying) number of nominations will win an Oscar. The data from each individual session will be publicly available by going to a url like [url].com/answers/[unique id]. The overall data will also be available on a results page. So I've started writing my models file, and this is what I have so far:
from django.db import models
class Nominee(models.Model):
award = models.CharField(max_length=50)
title = models.CharField(max_length=50)
key = modelsCharField(max_length=50)
subtitle = models.CharField(max_length=50)
numVotes = models.IntegerField()
class Session(models.Model):
id = models.IntegerField() # unique id of visitor
bpictureVote = models.ForeignKey(Nominee, related_name = 'nom')
bactorVote = models.ForeignKey(Nominee, related_name = 'nom')
# ... for each award
I was originally thinking of having
class Award(models.Model):
name = models.CharField(max_length=50)
and at the beginning of Nominee,
award = models.ForeignKey(Award, related_name = 'award')
but I couldn't figure out why that would be better than just having award be a part of the Nominee class.
This is really just a start, because I've gotten a bit stuck. Am I on the right track? Should I be doing this totally differently (as I probably should...)? Any thoughts?
Thanks!
You are on the right track.
You need a separate Award class to avoid having to type in award's name every time you create a Nominee. By having a ForeignKey reference you make sure that you can safely rename your award, add additional information about the award (let's say in the future you decide to give each award a separate page with a description and a list of nominees), you also avoid errors which can happen from having a set of different spellings and typos ("Best Engineer Award" and "Best Engineer award"). It also makes sense - your application operates a set of objects: user sessions, nominees and awards.
Few unrelated notes:
You don't need an explicit Session.id field, django ORM creates it for you.
Property names have to be name_with_underscores, not camelCase.
No spaces around "=" in an arguments list: models.ForeignKey(Nominee, related_name='nom').
4 spaces instead of 2 (unless explicitly otherwise specified).
I am not entirely sure, because you do mention multiple nominees per award (assuming this is something like a poll before the actual nomination) a ManyToMany would be your required relation, in order to use also the additional user data.
But in the case you have implemented this as a specific app for nominations and implemented a custom user model then this would be refactored to something else...
Anyway to your current implementation:
class Nominee(models.Model):
title = models.CharField(max_length=50)
key = modelsCharField(max_length=50)
subtitle = models.CharField(max_length=50)
...
class Award(models.Model):
name = models.CharField(max_length=50)
nominees = models.ManyToManyField(Nominee, through='AwardNominees')
...
class AwardNominees(models.Model):
nominee = models.ForeignKey(Nominee)
award = models.ForeignKey(Award)
user = models.ForeignKey(User)
numVotes = models.IntegerField()
....
So it turned out I was thinking about this entirely wrong. I've now completely changed things, and now it's fully functional (!). But in the spirit of full disclosure, I should say that it definitely may not be the best solution. It sure seems like a good one, though, because it's really simple. Now I have only one model:
class Vote(models.Model):
award = models.CharField(...) # Name of the award
title = models.CharField(...) # Title of the nominee
subtitle = models.CharField(...) # Subtitle of the nominee
uid = models.CharField(...) # A 6 character user ID for future access
When I want to show the results of one user's votes, I can use Django's database tools to filter for a certain uid captured in the URL. When I want to tally the votes, I can use a combination of filters and Django's count() to determine how many votes each nominee had for a certain award. Sounds reasonable enough to me!

How to model multilingual objects in Python using webapp2

I build a multilingual web app using Python and webapp2.
I have an object called Tag, which has translations to multiple languages. For this reason, I have created the following models:
class Language(ndb.Model):
code = ndb.StringProperty()
name = ndb.StringProperty(indexed=False)
class MultilingualText(ndb.Model):
language = ndb.KeyProperty(kind=Language)
text = ndb.TextProperty(indexed=False)
class Tag(ndb.Model):
translations = ndb.StructuredProperty(MultilingualText, repeated=True, indexed=False)
I would like to ask if this is the correct way to do such task, and how this structure can be used along with WTForms for validation, etc.
Thanks a lot in advance!
I think the best implementation can change depending on your goal and my answer here may not fulfill your needs.
For Language class, I would rather not use datastore for this purpose. I would use babel.Locale for determining display names.
As Tim said in the comment, I prefer using language code as the entity key. Here is an example Tag implementation, assuming every Tag needs a urlsafe slug.
def get_urlsafe_slug_from_tag(tag_text):
# ...
# ...
class Slug(ndb.Model):
# use urlsafe slug as the key_name
# You can optionally use the property bellow.
available_translations = ndb.StringProperty(repeated=True)
class Tag(ndb.Model):
# use language code as the key_name
text = ndb.TextProperty()
When a tag is newly created, I will create two entities; a Slug entity with a unique urlsafe string (slug) for that tag as the key, as well as a Tag entity with the language code as the key and this Slug entity as its parent.
In this example, there is a property named available_translations, which will allow you to negotiate with the users language choices, and even execute a query which will return Slugs with a translation for a specified language (e.g. list Slugs with Japanese translation).
For WTForm validation, can you tell me how you want to validate the post data? I think you will be able to get better answer if you share your detailed needs.

Categories