I'm using Django 1.7 and I have a problem with my fixtures.
I would like Django to use the default value or use the save() method to create unspecified values.
Here are my current objects:
# File: uuidable.py
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
class Uuidable(models.Model):
uuid = models.CharField(_('uuid'), blank=True,
null=False, unique=True,
max_length=64, default=uuid.uuid4()) # Tried here
class Meta:
abstract = True
def save(self, *args, **kwargs):
if self.pk is None:
self.uuid = uuid.uuid4() # Tried here also
super().save(*args, **kwargs)
# File: timestampable.py
from django.db import models
from django.utils.translation import ugettext_lazy as _
class Timestampable(models.Model):
created_at = models.DateTimeField(_('created at'), auto_now_add=True)
updated_at = models.DateTimeField(_('updated at'), auto_now=True)
class Meta:
abstract = True
# File: post.py
from project.lib.models.timestampable import Timestampable
from project.lib.models.uuidable import Uuidable
class Post(Timestampable, Uuidable):
title = models.CharField(_('title'), max_length=250, blank=False)
content = models.TextField(_('content'))
def __str__(self):
return self.title
As you can see, when I generate a new Post(), the created_at, updated_at and uuid values are automatically created on save(). But when I use fixtures, I get the following error:
[...]initial_data.yaml': Could not load post.Post(pk=None): UNIQUE constraint failed: post_post.uuid
If I specify a uuid in my fixture file, then I get an error on created_at and then on updated_at. So I have to specify the content of each field, even though I want it to be "automatic".
From the documentation (why is this in the django admin docs ?!), I know that the save() method is not called so this is why everything I put into the save() method doesn't work. But shouldn't the default or auto_now* features be enables/used ?
When fixture files are processed, the data is saved to the database as is. Model defined save() methods are not called, and any pre_save or post_save signals will be called with raw=True since the instance only contains attributes that are local to the model. You may, for example, want to disable handlers that access related fields that aren’t present during fixture loading and would otherwise raise an exception
Is there a way to "force" Django to automatically use the default or auto_now* features for fixtures ? I'm using manage.py syncdb to create all the tables etc.
I have searched on google and stack overflow but couldn't seem to find the right search keywords.
UPDATE-1: The following google group discussion says that objects are saved in raw mode, meaning that auto_now* features are not taken into account. I'm still searching to see if there is a way to hook some model function to the Django fixture saving.
The solution was to use django signals:
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.db.models.signals import pre_save
from django.dispatch import receiver
class Uuidable(models.Model):
uuid = models.CharField(_('uuid'), blank=True,
null=False, unique=True,
max_length=64, default=uuid.uuid4())
class Meta:
abstract = True
#receiver(pre_save)
def set_uuid_on_save(sender, instance, *args, **kwargs):
if instance.pk is None:
instance.uuid = uuid.uuid4()
That way, the model/data is populated whatever way you create the model (via shell, fixtures, whatever).
Automatically loading initial data fixtures is deprecated in Django 1.7. One solution is via signals as you mentioned.
Another one that I prefer is to create a python script where you create all the needed data, and execute it in the shell:
python manage.py shell < create_initial_data.py
I think that the problem is when you put default=uuid.uuid4(). The parenthesis are too much, because they imply that you pass the result of uuid.uuid4() to default argument and not the function itself, so you should put default=uuid.uuid4.
Related
I wrote tests for my model, but there is an error. I cannot fix that.
The error message AttributeError: 'Category' object has no attribute 'get_impath'
from django.test import TestCase
from .models import *
class CategoryTest(TestCase):
def setUp(self):
Category.objects.create(name='Python', impath='img.png')
def test_category_models(self):
category_test = Category.objects.get(name='Python')
self.assertEqual(
category_test.get_impath(), "img.png")
Here is my model
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=15)
impath = models.CharField(max_length=64)
Category.objects.create(name='Python', impath='img.png')
I assume this means you have something like
class Category(models.Model):
name = models.CharField(max_length=255)
impath = models.ImageField()
Here impath is an attribute. Django does not automatically create a method named get_impath(). This means you need to do category_test.impath instead of category_test.get_impath(). Fields in your model are attributes and there are no get methods for them unless you write them yourself.
Note that you are writing a test that verifies that Django works the way the documentation says it will. This is good for learning about how Django works and how to write tests in the Django environment. However, beyond that, such tests are not worth very much because you should assume that the authors of Django have already tested it.
I am using Django 2.0.8 and Python 3.5 for a project. I have different models in my project, some of which, I want to allow commenting on - with both the object (e.g. a blogpost) and comments to the blogpost being likeable.
I am using the threaded comments django app to provide commenting functionality.
Assuming I have a model Foo (see below):
from django.db import models
from django.conf import settings
class Foo(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, blank=False, null=False, default=1, on_delete = models.PROTECT)
# ...
class Likeable():
pass
class Rateable():
pass
How could I use mixins (or any other mechanism for that matter), to make the object Foo "commentable" (i.e. an object which can be commented upon), "likeable" (i.e. an object which can be commented upon) and "rateable" (i.e. an object which can be rated?)- bearing in mind that comments on an objects may be BOTH liked and rated.
According to django documentation , you can achieve this using the Content types Framework. ContentType is a generic model that permits you to track all the models included in INSTALLED_APPS using for that their app_label, model_name and pk. The way it works is easy:
Your generic Comment model
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.conf import settings
class Comment(models.Model):
# Generic relation fields
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
# Model specific fields
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
comment = models.TextField()
created = models.DatetimeField(auto_now_add=True)
# ...
Your reusable generic relation model. The best way is using abstract model classes or mixins. For example, using abstract models:
from django.db import models
from django.contrib.contenttypes.fields import GenericRelation
class Commentable(models.Model):
comments = GenericRelation(Comment)
class Meta:
abstract = True
Your Commentable model:
from django.db import models
class Foo(Commentable, Likeable, ...):
# your stuff
How to use it:
# Add a new comment to Foo
foo = new Foo()
foo.save()
foo.comments.create(author=author, comment="Your comment")
# Retrieve all comments from an specific user no matter the base model
comments = Comment.objects.filter(author=author)
EDIT As #ozren1983 said, each approach has its own downsides, but this is the standard way to do it.
The main advantages are:
You can retrieve all the comments (for example) made in all your commentable models in just one query. Using the approach of having a comment, like, etc table per model, you would need to concatenate a query per model. This could be problematic and a bit challenging if you have a lot of models or if you want to merge the results and order them, for example.
Just one table per functionality (comments, likes) implies just one database migration in case of change. This could be key if your database is huge.
The main disadvantage is the lack of integrity checks of this generic relationship in database. But if you plan to use the django ORM strictly, nothing should be broken.
BONUS: Another approach that many projects use is inheriting the models (one to one relationship) from an specific one called Item or Thread. Then, you can add all the comments, likes, etc functionalities to this model. This is called multi-table inheritance. An example:
from django.db import models
class Thread(models.Model):
pass
class Comment(models.Model):
# Relation with thread
thread = models.ForeignKey(
Thread,
on_delete=models.CASCADE,
related_name="comments"
)
# Model specific fields
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
comment = models.TextField()
created = models.DatetimeField(auto_now_add=True)
# ...
class Foo(Thread):
pass
Unlike using the generic relationships, the main advantage of this method is that, this way, you have database integrity checks.
The main disadvantage is that your database structure could become complex.
Based on my experience and recommendations in Two scoops of Django, I would advise against using GenericForeignKey and GenericRelation. Two big downsides of that approach are:
slow queries
danger of data corruption
Instead, I would use following approach. Let's say you have 3 models:
class User(models.Model):
username = models.CharField(max_length=255)
class Author(models.Model):
name = models.CharField(max_length=255)
class Post(models.Model):
title = models.CharField(max_length=255)
author = models.ForeignKey(Author)
Add abstract Like model, and use it as base class for other models that will implement liking functionality.
class Like(models.Model):
user = models.ForeignKey(User)
date_created = models.DateTimeField(auto_now_add=True)
class Meta:
abstract = True
class AuthorLike(Like):
author = models.ForeignKey(Author)
class PostLike(Like):
post = models.ForeignKey(Post)
Similarly, add abstract Rating model and use it as a base class:
class Rating(models.Model):
user = models.ForeignKey(User)
rate = models.PositiveSmallIntegerField()
date_created = models.DateTimeField(auto_now_add=True)
class Meta:
abstract = True
class AuthorRating(Rating):
author = models.ForeignKey(Author)
class PostRating(Rating):
post = models.ForeignKey(Post)
You can use same approach to enable liking and rating to the Comments model you are using:
from threadedcomments.models import ThreadedComment
class ThreadedCommentRating(Rating):
threadedcomment = models.ForeignKey(ThreadedComment)
class ThreadedCommentLike(Like):
threadedcomment = models.ForeignKey(ThreadedComment)
The django-contrib-comments app, according to documentation, makes use of GenericForeignKey, meaning its own model can create a relation to any other model in your project.
A simple solution would be to just copy that existing functionality, creating your own Like/Rate application based on the same concept (i.e. storing the Like/Rate models in that application's models).
I think you would get very far starting out by forking the https://github.com/django/django-contrib-comments codebase.
(I assume you have searched and failed to find an already existing application that already does this).
I'm not sure if this is a bug, limitation or error on my part, but I would like to get feedback on if there is a solution or work around to this issue...
In Django Admin, I have created a many-to-many inline based on a 'through' model ReleaseCardCount. The inline is linked to a parent class Card which is used for numerous child models. In Django Admin, I am only interested in configuring the child classes of Card, for example, ActionCard.
If I configure Django Admin for the parent Card model and ReleaseCardCount inline, I am able to add, remove and modify parent and inline objects without issue. If I configure the same set up for a child object of card, for example ActionCard, I can add inline objects if there are none or delete all inline objects, but I am unable to modify ActionCard or an inline object if an inline exists. If an inline object is present and I attempt to modify and save any data I get a "Please correct the error below" message, with no error present.
I've minimised the code needed to recreate the issue to the following...
models.py:
from django.db import models
class Release2(models.Model):
release_id = models.AutoField(primary_key=True)
release_name = models.CharField(max_length=100)
def __str__(self):
return self.release_name
class Card2(models.Model):
card_id = models.AutoField(primary_key=True)
card_name = models.CharField(max_length=100)
release_card_count = models.ManyToManyField( Release2, through='ReleaseCardCount2',)
def __str__(self):
return self.card_name
class ReleaseCardCount2(models.Model):
rcc_id = models.AutoField(primary_key=True)
rcc_release = models.ForeignKey(Release2, on_delete=models.CASCADE)
rcc_card = models.ForeignKey(Card2, on_delete=models.CASCADE)
rcc_count = models.PositiveIntegerField(default=1, null=True, blank=True)
class ActionCard2(Card2):
action_card_id = models.AutoField(primary_key=True)
action_name = models.CharField(max_length=100)
def __str__(self):
return self.card_name
admin.py:
from .models import (Release2, Card2, ReleaseCardCount2, ActionCard2)
class ReleaseCardCount2InLine(admin.TabularInline):
model = ReleaseCardCount2
extra = 1
class Card2Admin(admin.ModelAdmin):
inlines = (ReleaseCardCount2InLine,)
model = Card2
class ActionCard2Admin(admin.ModelAdmin):
inlines = (ReleaseCardCount2InLine,)
model = ActionCard2
admin.site.register(Release2)
admin.site.register(Card2, Card2Admin)
admin.site.register(ActionCard2, ActionCard2Admin)
Of interest, in my troubleshooting, I dropped and recreated the database in a dev environment. I found that one of the child models that was not working before, suddenly began to work. I suspect, that it worked due to being the first child model I configured Django Admin for with the inline. To recreate the issue with the above code, you may need try running it FIRST on the parent Card model. Once you have added, saved then modified and saved an inline object on Card, you can try it on ActionCard.
Relevant environment info:
Django - 2.0.6
Python - 3.6.4
PostgreSQL - 10.4
I would like to implement a function that updates quantity in LibraryBook each time the admin adds a book in SingleBook on the admin site. I have been searching for means to do so but to no avail. Any pointers including links to documentation would be very much appreciated.
Here is my code:
#models.py
class LibraryBook(models.Model):
book_title = models.CharField(max_length=100, blank=False)
book_author_id = models.ForeignKey(BookAuthors, on_delete=models.CASCADE)
category = models.ForeignKey(BookCategory, on_delete=models.CASCADE)
quantity = models.IntegerField(blank=False, default=0)
number_borrowed = models.IntegerField(default=0)
def __unicode__(self):
return unicode(self.book_title)
class SingleBook(models.Model):
serial_number = models.CharField(primary_key=True , max_length=150, blank=False)
book_id = models.ForeignKey(LibraryBook, on_delete=models.CASCADE)
is_available_returned = models.BooleanField(default=True)
is_borrowed = models.BooleanField(default=False)
def __unicode__(self):
return unicode(self.book_id)
#admin.py
class SingleBookAdmin(admin.ModelAdmin):
list_display = ('book_id', 'serial_number')
class LibraryBookAdmin(admin.ModelAdmin):
list_display = ('book_title', 'book_author_id', 'quantity')
search_fields = ('book_title', 'book_author_id')
fields = ('book_title', 'book_author_id', 'quantity')
PS: I have omitted the import and admin.site.register code
Django==1.9.8
django-material==0.8.0
django-model-utils==2.5.1
psycopg2==2.6.2
wheel==0.24.0
override save_model
If you only want to make the changes when an admin updates a record, the best way is to override the save_model method in ModelAdmin
The save_model method is given the HttpRequest, a model instance, a
ModelForm instance and a boolean value based on whether it is adding
or changing the object. Here you can do any pre- or post-save
operations.
class SingleBookAdmin(admin.ModelAdmin):
list_display = ('book_id', 'serial_number')
def save_model(self, request, obj, form, change):
admin.ModelAdmin.save_model(self, request, obj, form, change)
if obj.is_borrowed:
do something to obj.book_id.quantity
else:
do something to obj.book_id.quantity
post_save signal
from django.dispatch.dispatcher import receiver
from django.db.models.signals import post_save
#receiver(post_save, sender=SingleBook)
def user_updated(sender,instance, **kwargs):
''' Fired when a SingleBook is updated or saved
we will use the opporunity to change quantity'''
# your logic here
Other pointers
If on the other hand, you wanted to make changes based on all user actions, catching the post_save signal is the way to go. In either case, you might want to override the from_db method in the model to keep track of which fields have changed.
You might also want to change quantity and number_borrowed to IntegerFields (unless you are only using sqlite in which case it doesn't matter)
Also book_author_id should probably be book_author and book_id should probably be book (this is not a rule, just a convention to avoid the ugly book_id_id reference)
Use signals. Just attach post_save signal to SingleBook model and update according LibraryBook in it. post_save signal takes created argument, so you can determine if book is newly created or edited and apply your action based on that.
Also attach post_delete signal to decrease counter when SingleBook is removed.
To avoid race conditions (when 2 admins are adding books at the same time), I'm suggesting use of queryset update method together with F on changing LibraryBook counter, example:
LibraryBook.objects.filter(id=single_book.book_id_id).update(quantity=F('quantity') + 1)
Doing it that way will ensure that actual math operation will be performed on database level.
There are similar questions to this, but I believe mine is different. I am very new to Django and to Python, so please forgive my ignorance.
I have a custom class UserProfile that inherits from the django.contrib.auth.models User class. This UserProfile is based on the exercise in Tango with Django, however, I am using the example to create a different project/app.
I have UserProfile linked to the standard User model with a OneToOneField relationship in my models.py, as shown below:
class UserProfile(models.Model):
# Links UserProfile to a User model instance.
user = models.OneToOneField(User)
# The additional attribute I wish to include that isn't in User.
slug = models.SlugField(unique=True)
In my admin.py file, I want an interface for UserProfile that I can work with, and I want the slugfield to autopopulate when I enter a new UserProfile. I want it to autopopulate based on the username attribute of User. However, I can't seem to make it work. Here is my admin.py code:
class UserProfileAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("user.username",)}
When I try to runserver from my command line, I get the following error:
ERRORS: <class 'climbcast.admin.UserProfileAdmin'>: (admin.E030) The
value of >'prepopula ted_fields["slug"][0]' refers to 'user.username',
which is not an attribute of >' climbcast.UserProfile'.
System check identified 1 issue (0 silenced).
It won't allow me to access the user.username attribute this way, even though I can access it that way in the python shell. Any ideas on how to make this work?
Unfortunately prepopulated_fields doesn’t accept DateTimeField, ForeignKey, nor ManyToManyField fields.
Source: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.prepopulated_fields
Possible solution, in your models.py (make slug attribute optional):
from django.utils.encoding import force_text
from django.template.defaultfilters import slugify
class UserProfile(models.Model):
[...]
slug = models.SlugField(blank=True, db_index=True, unique=True)
def get_unique_slug(self, value):
"""
Generate a valid slug for a for given string.
"""
qs = self.__class__.objects.all()
used = qs.values_list('slug', flat=True)
i = 1
baseslug = slugify(value)
while slug in used:
slug = '%s-%s' % (baseslug, i)
i += 1
return slug
def save(self, *args, **kwargs):
if not self.slug and self.user:
self.slug = self.get_unique_slug(force_text(self.user.username))
super(UserProfile, self).save(*args, **kwargs)