What is the best way to set a default value for a foreign key field in a model? Suppose I have two models, Student and Exam with student having exam_taken as foreign key. How would I ideally set a default value for it? Here's a log of my effort
class Student(models.Model):
....
.....
exam_taken = models.ForeignKey("Exam", default=1)
Works, but have a hunch there's a better way.
def get_exam():
return Exam.objects.get(id=1)
class Student(models.Model):
....
.....
exam_taken = models.ForeignKey("Exam", default=get_exam)
But this fails with tables does not exist error while syncing.
Any help would be appreciated.
I would modify #vault's answer above slightly (this may be a new feature). It is definitely desirable to refer to the field by a natural name. However instead of overriding the Manager I would simply use the to_field param of ForeignKey:
class Country(models.Model):
sigla = models.CharField(max_length=5, unique=True)
def __unicode__(self):
return u'%s' % self.sigla
class City(models.Model):
nome = models.CharField(max_length=64, unique=True)
nation = models.ForeignKey(Country, to_field='sigla', default='IT')
As already implied in #gareth's answer, hard-coding a default id value might not always be the best idea:
If the id value does not exist in the database, you're in trouble. Even if that specific id value does exist, the corresponding object may change. In any case, when using a hard-coded id value, you'd have to resort to things like data-migrations or manual editing of existing database content.
To prevent that, you could use get_or_create() in combination with a unique field (other than id).
Here's one way to do it:
from django.db import models
class Exam(models.Model):
title = models.CharField(max_length=255, unique=True)
description = models.CharField(max_length=255)
#classmethod
def get_default_pk(cls):
exam, created = cls.objects.get_or_create(
title='default exam',
defaults=dict(description='this is not an exam'),
)
return exam.pk
class Student(models.Model):
exam_taken = models.ForeignKey(
to=Exam, on_delete=models.CASCADE, default=Exam.get_default_pk
)
Here an Exam.title field is used to get a unique object, and an Exam.description field illustrates how we can use the defaults argument (for get_or_create) to fully specify the default Exam object.
Note that we return a pk, as suggested by the docs:
For fields like ForeignKey that map to model instances, defaults should be the value of the field they reference (pk unless to_field is set) instead of model instances.
Also note that default callables are evaluated in Model.__init__() (source). So, if your default value depends on another field of the same model, or on the request context, or on the state of the client-side form, you should probably look elsewhere.
I use natural keys to adopt a more natural approach:
<app>/models.py
from django.db import models
class CountryManager(models.Manager):
"""Enable fixtures using self.sigla instead of `id`"""
def get_by_natural_key(self, sigla):
return self.get(sigla=sigla)
class Country(models.Model):
objects = CountryManager()
sigla = models.CharField(max_length=5, unique=True)
def __unicode__(self):
return u'%s' % self.sigla
class City(models.Model):
nome = models.CharField(max_length=64, unique=True)
nation = models.ForeignKey(Country, default='IT')
In my case, I wanted to set the default to any existing instance of the related model. Because it's possible that the Exam with id 1 has been deleted, I've done the following:
class Student(models.Model):
exam_taken = models.ForeignKey("Exam", blank=True)
def save(self, *args, **kwargs):
try:
self.exam_taken
except:
self.exam_taken = Exam.objects.first()
super().save(*args, **kwargs)
If exam_taken doesn't exist, django.db.models.fields.related_descriptors.RelatedObjectDoesNotExist will be raised when a attempting to access it.
The issue with most of these approaches are that they use HARD CODED values or lambda methods inside the Model which are not supported anymore since Django Version 1.7.
In my opinion, the best approach here is to use a sentinel method which can also be used for the on_delete argument.
So, in your case, I would do
# Create or retrieve a placeholder
def get_sentinel_exam():
return Exam.objects.get_or_create(name="deleted",grade="N/A")[0]
# Create an additional method to return only the id - default expects an id and not a Model object
def get_sentinel_exam_id():
return get_sentinel_exam().id
class Exam(models.Model):
....
# Making some madeup values
name=models.CharField(max_length=200) # "English", "Chemistry",...
year=models.CharField(max_length=200) # "2012", "2022",...
class Student(models.Model):
....
.....
exam_taken = models.ForeignKey("Exam",
on_delete=models.SET(get_sentinel_exam),
default=get_sentinel_exam_id
)
Now, when you just added the exam_taken field uses a guaranteed existing value while also, when deleting the exam, the Student themself are not deleted and have a foreign key to a deleted value.
You could use this pattern:
class Other(models.Model):
DEFAULT_PK=1
name=models.CharField(max_length=1024)
class FooModel(models.Model):
other=models.ForeignKey(Other, default=Other.DEFAULT_PK)
Of course you need to be sure that there is a row in the table of Other. You should use a datamigration to be sure it exists.
I'm looking for the solution in Django Admin, then I found this:
class YourAdmin(admin.ModelAdmin)
def get_changeform_initial_data(self, request):
return {'owner': request.user}
this also allows me to use the current user.
see django docs
the best way I know is to use lambdas
class TblSearchCase(models.Model):
weights = models.ForeignKey('TblSearchWeights', models.DO_NOTHING, default=lambda: TblSearchWeights.objects.get(weight_name='value_you_want'))
so you can specify the default row..
default=lambda: TblSearchWeights.objects.get(weight_name='value_you_want')
Related
I Have an accounts table with account number and account name in a model called accounts
how do I create a lookup such that whenever I enter an account number in django template, account name get populated automatically
my models at attempt are
class Account(models.Model):
account_number = models.IntegerField()
account_name = models.CharField(max_length=50)
bank_name = models.ForeignKey(Bank, on_delete=models.CASCADE)
status = models.CharField(max_length=50)
def __str__(self):
return account_name
def get_absolute_url(self):
return reverse_lazy('accounts')
class AccountLookup(models.Model):
account_number = models.ForeignKey(Account, on_delete=models.CASCADE):
account_name = models. ???????????
If you want something you can use in a template that will render account name starting from an AccountLookup object, then its
{{accountlookup_instance.account_number.account_name}}
Note that this will hit the DB again, unless you used select_related() on the queryset which obtained the AccountLookup object(s) in the first place.
Python code can likewise use this dotted path, with the same caveat.
You might regard it as a simplification to be able to refer to what looks like a field (but isn't). In which case you define it as a property
class AccountLookup(models.Model):
account_number = models.ForeignKey(Account, on_delete=models.CASCADE)
...
#property
def account_name(self):
return self.account_number.account_name
(It gets more useful if you need to apply some standard formatting to the account name to convert it into a more human-readable form in this context).
By the way, calling it account_number is confusing. Better to just name a ForeignKey field for what it is. account in this case: the account object linked by a ForeignKey to this object. Yes, it's represented internally by an account_id which is commonly an integer (the auto-generated primary key), but that's a low level detail Django programmers are not often concerned with.
Is there a way to return a different set value for a django model field if that field has a null value?
I have users with profile images (image_url), for users who do not have one I'd like to return a default no avatar url, rather than 'None'.
class User(models.Model):
name = models.CharField(max_length=800)
image_url = models.URLField(max_length=2000, blank=True, null=True)
The easiest way is to just add an extra property to the model:
#property
def image_url_with_default(self):
return self.image_url or whatever_the_default_url_is
as #paulo-scardine suggested, just add a condition when you want to actually use it - i.e in a template, or if it's bound to be used in multiple places, as a method/property of the model (as #remcogerlich) suggested.
#property
def avatar(self):
return self.image_url or settings.DEFAULT_AVATAR_URL
Have you tried using the default option on the URLField?
you could do someting like:
image_url = models.URLField(max_length=2000, blank=True, null=True, default='http://misyte.com/default-image.jpg')
Haven't tested it... there's also an ImageField on django.. maybe you could check that too
Hope this helps
You could override the __getattribute__ method, per this answer. This has the advantage of not introducing an additional property (that you'll need to remember, or update any relevant references for):
class User(models.Model):
....
def __getattribute__(self, name):
attr = models.Model.__getattribute__(self, name)
if name == 'avatar' and not attr:
return 'path/to/default/img'
return attr
Oh, my... URL entered by the user... Looks dangerous.
class User(models.Model):
name = models.CharField(max_length=800)
image = models.ImageField(upload_to='avatars', blank=True, null=True)
def avatar(self):
if self.image: return self.image.url
return '/path/to/default/avatar.png'
If you store the image at your side you can at least run something like "nude" in order to analyze an image for nudity.
class Facilites(models.Model):
id = models.CharField(max_length=32, primary_key=True)
name = models.CharField(max_length=128)
class Objects(models.Model):
name = models.CharField(max_length=64)
facilityid = models.ForeignKey(Facilities)
class Admins(models.Model):
user = models.OneToOneField(User)
facilities = models.ManyToManyField(Facilities)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Admins.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
What i want is to have users (admins) only be able to add or modify "facilityid" in Objects to values specified in their Admins.facilities.
So if some user is named UserA and has facilities = ('FacA', 'FacB'), when he is adding a new object to DB, he shoudln't be able to add something like Object('Random object', 'FacC')
Also, he shouldn't be able to modify existing objects to facilities he doesn't belong to.
I have filtered the Objects with:
def queryset(self, request):
qs = super(ObjectsAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(facitityid__id__in = request.user.get_profile().facilities.all())
so users can only see the object that belong to their facilities. But i have no idea how to prevent them from adding/editing object out of their facilities.
edit:
found the answer here: https://stackoverflow.com/a/3048563/1421572
It turns out that ModelAdmin.formfield_for_foreignkey was the right answer in this situation: http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey
I would do this with either a pre-made facility list (i.e. You could create an integer field that is hooked to FACILITY_CHOICES for the user to select from.)
If only admins can do it then permissions sounds quite viable. You can also do form validation to check for errors against the db. Depending on how many facilities you have you may want a different approach.
You can do this same technique with a models.CharField as well. So perhaps assign a 3 letter facility code to each facility and require the entry to match one of the 3 letter strings. You could even have the list in a .txt file to read from. There are really so many ways to do this. I will provide an example of a pre-made facility list and accessing the facility a particular user belongs to from the api / template:
NYC_FACILITY = 0
LA_FACILITY = 1
ATL_FACILITY = 2
FACILITY_CHOICES = (
(NYC_FACILITY, 'NYC'),
(LA_FACILITY, 'LA'),
(ATL_FACILITY, 'ATL'),
class Facility(models.Model):
name = models.IntegerField(choices=FACILITY_CHOICES, default="NYC")
class Meta:
order_by = ['name']
verbose_name_plural = "facilities"
verbose_name = "facility"
def __unicode__(self):
return self.name
As far as viewing the facilities page that a particular user belongs to you will have a m2m one to one or FK relationship between the objects. If FK or m2m relationship then you will have access to additional methods of that model type. get_related However, I'm not going to use get_related in my example. Once you are in an instance you then have access to entry_set.
# models.py
from django.auth import User
class Person(User):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
facility_loc = models.ForeignKey('Facility') # ForeignKey used assuming only one person can belong to a facility.
slug = models.SlugField(unique=True)
def get_absolute_url(self):
return "/%s/%s/" % self.facility_loc % self.slug
# views.py - TemplateView is automatically given a context variable called params which parses data from the URL. So, I'll leave the regex in the URLConf up to you.
class UserFacilityView(TemplateView):
model = Facility
template_name = "user_facility.html"
Now in your template you should be able to access facility_set from a User instance or user_set from a facility instance.
I'm doing something that doesn't feel very efficient. From my code below, you can probably see that I'm trying to allow for multiple profiles of different types attached to my custom user object (Person). One of those profiles will be considered a default and should have an accessor from the Person class. Storing an is_default field on the profile doesn't seem like it would be the best way to keep track of a default, is it?
from django.db import models
from django.contrib.auth.models import User, UserManager
class Person(User):
public_name = models.CharField(max_length=24, default="Mr. T")
objects = UserManager()
def save(self):
self.set_password(self.password)
super(Person, self).save()
def _getDefaultProfile(self):
def_teacher = self.teacher_set.filter(default=True)
if def_teacher: return def_teacher[0]
def_student = self.student_set.filter(default=True)
if def_student: return def_student[0]
def_parent = self.parent_set.filter(default=True)
if def_parent: return def_parent[0]
return False
profile = property(_getDefaultProfile)
def _getProfiles(self):
# Inefficient use of QuerySet here. Tolerated because the QuerySets should be very small.
profiles = []
if self.teacher_set.count(): profiles.append(list(self.teacher_set.all()))
if self.student_set.count(): profiles.append(list(self.student_set.all()))
if self.parent_set.count(): profiles.append(list(self.parent_set.all()))
return profiles
profiles = property(_getProfiles)
class BaseProfile(models.Model):
person = models.ForeignKey(Person)
is_default = models.BooleanField(default=False)
class Meta:
abstract = True
class Teacher(BaseProfile):
user_type = models.CharField(max_length=7, default="teacher")
class Student(BaseProfile):
user_type = models.CharField(max_length=7, default="student")
class Parent(BaseProfile):
user_type = models.CharField(max_length=7, default="parent")
First of all you could make things a lot more easy by not declaring the BaseProfile abstract:
from django.db import models
from django.contrib.auth.models import User, UserManager
class Person(User):
public_name = models.CharField(max_length=24, default="Mr. T")
objects = UserManager()
def save(self):
self.set_password(self.password)
super(Person, self).save()
def _getDefaultProfile(self):
try:
return self.baseprofile_set.get(default=True)
except ObjectDoesNotExist:
return False
profile = property(_getDefaultProfile)
def _getProfiles(self):
return self.baseprofile_set.all()
profiles = property(_getProfiles)
class BaseProfile(models.Model):
person = models.ForeignKey(Person)
is_default = models.BooleanField(default=False)
class Teacher(BaseProfile):
user_type = models.CharField(max_length=7, default="teacher")
class Student(BaseProfile):
user_type = models.CharField(max_length=7, default="student")
class Parent(BaseProfile):
user_type = models.CharField(max_length=7, default="parent")
The way this is nicer? Your properties didn't know anyway what type they were returning, so the abstract baseclass only made you have an incredible annoying overhead there.
If you now are wondering how the hell you can get the data from the specific profiles since I made anything returned BaseProfile? You can do something like this:
try:
#note the lowercase teacher referal
print myuser.profile.teacher.someteacherfield
except Teacher.DoesNotExist:
print "this is not a teacher object!"
Also I do hope you didn't use the user_type field solely for this purpose, because django has it built in better as you can see. I also hope you really have some other unique fields in your derived profile classes because otherwise you should throw them away and just past a usertype field into BaseProfile (look at choices to do this good).
Now as for the is_default, imho this method is as good as any. You can always try to add custom constraints to your dbms itself, saying there sould be 0 or 1 records containing the same FK and is_default=True (there is no django way to do this). What I also would say is, add a method make_default and in that method make sure the is_default is unique for that person (e.g. by first setting is_default to False on all profiles with the same FK). This will save you a lot of possible sorrow. You can also add this check in the save() method of BaseProfile.
Another way you could do it is by adding a Foreign Key to the Person Model that points to the default Profile. While this will ensure default to be unique on django level, it can also provide denormalization and corruption of your data, even on a more annoying level, so I'm no big fan of it. But again, if you do all adding/removing/updating of profiles through predefined methods (will be more complex now!) you should be safe.
Finally, maybe you have good reasons to inherit from User, but the default way to extend the User functionality is not this, it's described here.
Consider the following situation: -
Suppose my app allows users to create the states / provinces in their
country. Just for clarity, we are considering only ASCII characters
here.
In the US, a user could create the state called "Texas". If this app
is being used internally, let's say the user doesn't care if it is
spelled "texas" or "Texas" or "teXas"
But importantly, the system should prevent creation of "texas" if
"Texas" is already in the database.
If the model is like the following:
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
The uniqueness would be case-sensitive in postgres; that is, postgres
would allow the user to create both "texas" and "Texas" as they are
considered unique.
What can be done in this situation to prevent such behavior. How does
one go about providing case-insenstitive uniqueness with Django and
Postgres
Right now I'm doing the following to prevent creation of case-
insensitive duplicates.
class CreateStateForm(forms.ModelForm):
def clean_name(self):
name = self.cleaned_data['name']
try:
State.objects.get(name__iexact=name)
except ObjectDoesNotExist:
return name
raise forms.ValidationError('State already exists.')
class Meta:
model = State
There are a number of cases where I will have to do this check and I'm not keen on having to write similar iexact checks everywhere.
Just wondering if there is a built-in or
better way? Perhaps db_type would help? Maybe some other solution exists?
You could define a custom model field derived from models.CharField.
This field could check for duplicate values, ignoring the case.
Custom fields documentation is here http://docs.djangoproject.com/en/dev/howto/custom-model-fields/
Look at http://code.djangoproject.com/browser/django/trunk/django/db/models/fields/files.py for an example of how to create a custom field by subclassing an existing field.
You could use the citext module of PostgreSQL https://www.postgresql.org/docs/current/static/citext.html
If you use this module, the the custom field could define "db_type" as CITEXT for PostgreSQL databases.
This would lead to case insensitive comparison for unique values in the custom field.
Alternatively you can change the default Query Set Manager to do case insensitive look-ups on the field. In trying to solve a similar problem I came across:
http://djangosnippets.org/snippets/305/
Code pasted here for convenience:
from django.db.models import Manager
from django.db.models.query import QuerySet
class CaseInsensitiveQuerySet(QuerySet):
def _filter_or_exclude(self, mapper, *args, **kwargs):
# 'name' is a field in your Model whose lookups you want case-insensitive by default
if 'name' in kwargs:
kwargs['name__iexact'] = kwargs['name']
del kwargs['name']
return super(CaseInsensitiveQuerySet, self)._filter_or_exclude(mapper, *args, **kwargs)
# custom manager that overrides the initial query set
class TagManager(Manager):
def get_query_set(self):
return CaseInsensitiveQuerySet(self.model)
# and the model itself
class Tag(models.Model):
name = models.CharField(maxlength=50, unique=True, db_index=True)
objects = TagManager()
def __str__(self):
return self.name
a very simple solution:
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def clean(self):
self.name = self.name.capitalize()
Explicit steps for Mayuresh's answer:
in postgres do: CREATE EXTENSION citext;
in your models.py add:
from django.db.models import fields
class CaseInsensitiveTextField(fields.TextField):
def db_type(self, connection):
return "citext"
reference: https://github.com/zacharyvoase/django-postgres/blob/master/django_postgres/citext.py
in your model use: name = CaseInsensitiveTextField(unique=True)
On the Postgres side of things, a functional unique index will let you enforce unique values without case. citext is also noted, but this will work with older versions of PostgreSQL and is a useful technique in general.
Example:
# create table foo(bar text);
CREATE TABLE
# create unique index foo_bar on foo(lower(bar));
CREATE INDEX
# insert into foo values ('Texas');
INSERT 0 1
# insert into foo values ('texas');
ERROR: duplicate key value violates unique constraint "foo_bar"
Besides already mentioned option to override save, you can simply store all text in lower case in database and capitalize them on displaying.
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def save(self, force_insert=False, force_update=False):
self.name = self.name.lower()
super(State, self).save(force_insert, force_update)
You can use lookup='iexact' in UniqueValidator on serializer, like this:
class StateSerializer(serializers.ModelSerializer):
name = serializers.CharField(validators=[
UniqueValidator(
queryset=models.State.objects.all(),lookup='iexact'
)]
django version: 1.11.6
If you don't want to use a postgres-specific solution, you can create a unique index on the field with upper() to enforce uniqueness at the database level, then create a custom Field mixin that overrides get_lookup() to convert case-sensitive lookups to their case-insensitive versions. The mixin looks like this:
class CaseInsensitiveFieldMixin:
"""
Field mixin that uses case-insensitive lookup alternatives if they exist.
"""
LOOKUP_CONVERSIONS = {
'exact': 'iexact',
'contains': 'icontains',
'startswith': 'istartswith',
'endswith': 'iendswith',
'regex': 'iregex',
}
def get_lookup(self, lookup_name):
converted = self.LOOKUP_CONVERSIONS.get(lookup_name, lookup_name)
return super().get_lookup(converted)
And you use it like this:
from django.db import models
class CICharField(CaseInsensitiveFieldMixin, models.CharField):
pass
class CIEmailField(CaseInsensitiveFieldMixin, models.EmailField):
pass
class TestModel(models.Model):
name = CICharField(unique=True, max_length=20)
email = CIEmailField(unique=True)
You can read more about this approach here.
You can do this by overwriting the Model's save method - see the docs. You'd basically do something like:
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def save(self, force_insert=False, force_update=False):
if State.objects.get(name__iexact = self.name):
return
else:
super(State, self).save(force_insert, force_update)
Also, I may be wrong about this, but the upcoming model-validation SoC branch will allow us to do this more easily.
Solution from suhail worked for me without the need to enable citext, pretty easy solution only a clean function and instead of capitalize I used upper(). Mayuresh's solution also works but changed the field from CharField to TextField.
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def clean(self):
self.name = self.name.upper()