I've noticed the strange behaviour of default value in django model. For example we have a simple django model:
import uuid
...
class SiteUser(models.Model):
...
username = models.CharField(max_length=255, verbose_name=u"Username")
activation_key = models.CharField(max_length=64, verbose_name=u"Activation key", default=uuid.uuid1())
When I create a new user, and after that another one like that:
user_a = SiteUser(username="UserA")
user_a.save()
user_b = SiteUser(username="UserB")
user_b.save()
Django makes 2 users with the same activation_key
But then I do it like that:
user_a = SiteUser(username="UserA")
user_a.activation_key = uuid.uuid1()
user_a.save()
user_b = SiteUser(username="UserB")
user_b.activation_key = uuid.uuid1()
user_b.save()
Everything works fine and Django creates 2 users with different activation keys.
What's going on here? Python loads model object and calculate the default value of the model when the wsgi app starts up or that? Why uuid gives the same values in the first case but different in second?
Thanks.
Problem is the default attribute that you are setting as
activation_key = models.CharField(max_length=64, verbose_name=u"Activation key",
default=uuid.uuid1())
Here you are setting the default value as not a callable but value returned by uuid.uuid1() call when this model class is initialized.
You should set it as default=uuid.uuid1 which sets it as callable, and is sets new uuid each time new default value needs to be used.
As of Django 1.8, there is a new UUIDField available. It's described in the following link which also covers how to set defaults:
https://docs.djangoproject.com/en/1.8/ref/models/fields/#uuidfield
Related
I have a WTForm with the following:
class MyForm(FlaskForm):
...
timestamp = DateTimeField("Timestamp", default = datetime.utcnow, validators = [Required()])
name = StringField("Name", default = str(int(datetime.utcnow().timestamp())), validators = [Optional()])
...
Upon creating a new form the default timestamp field updates as expected while the default name field continues to use the timestamp of when the app was started. I can successfully set the timestamp in my route, my preference is to utilize the default functionality of the form. Looking here:
The Field base class
It states that default "May be a callable." but I haven't been able to find a working example of this. I'm assuming if I make this a callable, the default value will update with current timestamp. Does anyone know how to make default get its value from a function?
Thanks in advance,
Brian
In your timestamp field, you provide a callable - datetime.datetime.utcnow to default, so each time the form is instantiated the callable is called and the new result is used.
In your name field, datetime.datetime.utcnow().timestamp() is evaluated when the form is compiled, so the default value is the same for every instance of the form.
If you want the default value of name to be evaluated every time the form is instantiated, pass it a function (a callable) that returns datetime.datetime.utcnow().timestamp().
Like this:
def get_default():
datetime.datetime.utcnow().timestamp()
class MyForm(FlaskForm):
timestamp = DateTimeField("Timestamp", default=datetime.utcnow, validators=[Required()])
name = StringField("Name", default=get_default, validators=[Optional()])
or if you prefer, you can use a lambda:
class MyForm(FlaskForm):
timestamp = DateTimeField("Timestamp", default=datetime.utcnow, validators=[Required()])
name = StringField("Name", default=lambda : datetime.datetime.utcnow().timestamp(), validators=[Optional()])
Does django enforce uniqueness for a primary key?
The documentation here seems to suggest so, but when I define a class as:
class Site(models.Model):
id = models.IntegerField(primary_key=True)
and test this constraint in a test case:
class SiteTestCase(TestCase):
def setUp(self):
self.site = Site(id=0, name='Site')
self.site.save()
def tearDown(self):
self.site.delete()
def test_unique_id(self):
with self.assertRaises(IntegrityError):
badSite = Site(id=0, name='Bad Site')
badSite.save()
badSite.delete()
the test fails.
If I test on a normal field (primary_key=False, unique=True) then the exception is raised correctly. Setting unique=True on the id field does not change the result.
Is there something about primary_key fields that I'm missing here?
My database backend is MySQL, if that's relevant.
Your test method is wrong. What you're doing here is updating the existing instance since you're supplying an already used primary key. Change the save to a force_insert like so.
def test_unique_id(self):
with self.assertRaises(IntegrityError):
badSite = Site(id=0, name='Bad Site')
badSite.save(force_insert=True)
badSite.delete()
The django docs explain how django knows whether to UPDATE or INSERT. You should read that section.
Are you aware that django already supports automatic primary keys? See the documentation for more of an explanation.
In my Django project, all entities deleted by the user must be soft deleted by setting the current datetime to deleted_at property. My model looks like this: Trip <-> TripDestination <-> Destination (many to many relation). In other words, a Trip can have multiple destinations.
When I delete a Trip, the SoftDeleteManager filters out all the deleted trip. However, if I request all the destinations of a trip (using get_object_or_404(Trip, pk = id)), I also get the deleted ones (i.e. TripDestination models with deleted_at == null OR deleted_at != null). I really don't understand why since all my models inherit from LifeTimeTracking and are using the SoftDeleteManager.
Can someone please help me to understand why the SoftDeleteManager isn't working for n:m relation?
class SoftDeleteManager(models.Manager):
def get_query_set(self):
query_set = super(SoftDeleteManager, self).get_query_set()
return query_set.filter(deleted_at__isnull = True)
class LifeTimeTrackingModel(models.Model):
created_at = models.DateTimeField(auto_now_add = True)
updated_at = models.DateTimeField(auto_now = True)
deleted_at = models.DateTimeField(null = True)
objects = SoftDeleteManager()
all_objects = models.Manager()
class Meta:
abstract = True
class Destination(LifeTimeTrackingModel):
city_name = models.CharField(max_length = 45)
class Trip(LifeTimeTrackingModel):
name = models.CharField(max_length = 250)
destinations = models.ManyToManyField(Destination, through = 'TripDestination')
class TripDestination(LifeTimeTrackingModel):
trip = models.ForeignKey(Trip)
destination = models.ForeignKey(Destination)
Resolution
I filed the bug 17746 in Django Bug DB. Thanks to Caspar for his help on this.
It looks like this behaviour comes from the ManyToManyField choosing to use its own manager, which the Related objects reference mentions, because when I try making up some of my own instances & try soft-deleting them using your model code (via the manage.py shell) everything works as intended.
Unfortunately it doesn't mention how you can override the model manager. I spent about 15 minutes searching through the ManyToManyField source but haven't tracked down where it instantiates its manager (looking in django/db/models/fields/related.py).
To get the behaviour you are after, you should specify use_for_related_fields = True on your SoftDeleteManager class as specified by the documentation on controlling automatic managers:
class SoftDeleteManager(models.Manager):
use_for_related_fields = True
def get_query_set(self):
query_set = super(SoftDeleteManager, self).get_query_set()
return query_set.filter(deleted_at__isnull = True)
This works as expected: I'm able to define a Trip with 2 Destinations, each through a TripDestination, and if I set a Destination's deleted_at value to datetime.datetime.now() then that Destination no longer appears in the list given by mytrip.destinations.all(), which is what you are after near as I can tell.
However, the docs also specifically say do not filter the query set by overriding get_query_set() on a manager used for related fields, so if you run into problems later, bear this in mind as a possible cause.
To enable filtering by deleted_at field of Destinantion and Trip models setting use_for_related_fields = True for SoftDeleteManager class is enough. As per Caspar's answer this does not return deleted Destinations for trip_object.destinations.all().
However from your comments we can see you would like to filter out Destinations that are linked to Trip via a TripDestination object with a set deleted_at field, a.k.a. soft delete on a through instance.
Let's clarify the way managers work. Related managers are the managers of the remote model, not of a through model.
trip_object.destinantions.some_method() calls default Destination manager.
destinantion_object.trip_set.some_method() calls default Trip manager.
TripDestination manager is not called at any time.
You can call it with trip_object.destinantions.through.objects.some_method(), if you really want to. Now, what I would do is add an Instance method Trip.get_destinations and a similar Destination.get_trips that filters out deleted connections.
If you insist on using the manager to do the filtering it gets more complicated:
class DestinationManager(models.Manager):
use_for_related_fields = True
def get_query_set(self):
query_set = super(DestinationManager, self).get_query_set()
if hasattr(self, "through"):
through_objects = self.through.objects.filter(
destination_id=query_set.filter(**self.core_filters).get().id,
trip_id=self._fk_val,
deleted_at__isnull=True)
query_set = query_set.filter(
id__in=through_objects.values("destination_id"))
return query_set.filter(deleted_at__isnull = True)
The same would have to be done for TripManager as they would differ. You may check the performance and look at django/db/models/fields/related.py for reference.
Modifying the get_queryset method of the default manager may hamper the ability to backup the database and the documentation discourages it. Writing a Trip.get_destinations method is the alternative.
Let's assume that I have following models:
class ScoutBook(models.Model):
troop = models.ForeignKey('Dictionary', limit_choices_to={'type' : 'Troop'}, related_name='+', blank=True, null=True)
class Dictionary(models.Model):
name = models.CharField(max_length=CHAR_FIELD_MAX_LEN, verbose_name="Nazwa")
active = models.BooleanField(verbose_name="Aktywny")
type = models.CharField(max_length=CHAR_FIELD_MAX_LEN, choices=DICTIONARY_CHOICES)
and I want to implement following logic:
when creating ScoutBook allow users to select only active troops, and when editing allow to select active troops or allow user to leave value unchanged (even if the troop is inactive). If I use limit_choices_to = {..., 'active' = True} troop that is inactive is absent from combo box in django admin.
So to be clear: let's assume that there are four troops in this system: Troop1, Troop2 and InactiveTroop, InactiveTroop2. On model creation I would like user to be able to choose Troop1 and Troop2. If model has troop field set to InactiveTroop2, I would like user to be able to choose between InactiveTroop2, Troop1 and Troop2.
I was looking at the django forms api and I didn't found obvious way do this. Moreover, in the application I'm developing there will be many such fields and many such models --- so solution must be pain free. I would rather not create new Form class for every model. I will be using mostly django admin to enable editing the database, and some read only views that will just list entries.
Ideally I would like to encapsulate this functionality in some custom field --- but fields have access to model instance on validate and save phase --- so I dont have access to it when I produce formfield.
This sounds like something you want to do in a form, not in the object itself. Create a ModelForm and override the ModelChoiceField like this:
from django import forms
class ScoutBookForm(forms.ModelForm):
troop = forms.ModelChoiceField(queryset=Troop.objects.filter(active=True))
class Meta:
model = ScoutBook
You can also override the clean method of ScoutBook to ensure it cannot ever be saved with an inactive Troop, though that may have some unintended consequences (e.g., you wouldn't be able to update a ScoutBook in the admin if the troop had gone inactive at some point in the past).
Well I had to hook into ModelForm creation. Attached Form inspects it's fields and if specific conditions are met it replaces model field queryset.
class DictionayModelForm(ModelForm):
def __init__(self, *largs, **kwargs):
super(DictionayModelForm, self).__init__(*largs, **kwargs)
if self.instance and self.instance.pk is not None:
for f in self.instance._meta.fields:
if isinstance(f, models.ForeignKey) and issubclass(f.rel.to, Dictionary):
model_field = self.fields[f.name]
value = getattr(self.instance, f.name, None)
if value and value not in model_field.choices:
model_field.queryset = Dictionary.objects.filter(Q(**f.rel.limit_choices_to) | Q(id = value.id))
From all I've read, it appears this should Just Work, but it doesn't.
I have a custom model:
from django.db import models
from django.contrib.auth.models import *
class Feed(models.Model):
user = models.ForeignKey(User, blank=True, null=True)
link = models.CharField(max_length=200)
startDate = models.CharField(max_length=8)
endDate = models.CharField(max_length=8)
def __unicode__(self):
return str(self.id)
def __init__(self, link, sDate, eDate, user=None):
super(Feed, self).__init__()
self.link = link
self.startDate = sDate
self.endDate = eDate
self.user = user
And I'm also using the User model included in 'django.contrib.auth.models'.
When I create a feed, e.g.
feed = Feed(link, sDate, eDate)
feed.save()
(or a similar one with a user specified) it appears to store it in the database (I get its PK which keeps incrementing), but 'Feed.objects.all()' returns an empty list. Trying to filter by an existing pk also returns an empty list and trying to get() an existing pk gives me the following error
TypeError: __init__() takes at most 5 arguments (6 given)
Looking at how I should be retrieving objects from custom models, it appears that I've done everything I should, but that is clearly not the case...
Whoa.
Why are you overriding your model's __init__? There are very few good reasons to do this, and if you do, you must absolutely keep the interface the same- because that __init__ is called every time django pulls one of your models from the db (which is why you get the error when you call .get())
What are you hoping to accomplish with your __init__?
You should probably just delete your __init__ and then you can create Feed objects the normal, django way:
feed = Feed(link=link, startDate=sDate, endDate=eDate)
That line will create the correct feed object you want.
Did you try named arguments, e.g.
feed = Feed(link=link, startDate=sDate, endDate=eDate)
How did you use get()? It should also be used with named arguments, e.g.:
Feed.objects.get(pk=6)