class Foo(models.Model):
title = models.CharField(max_length=20)
slug = models.SlugField()
Is there a built-in way to get the slug field to autopopulate based on the title? Perhaps in the Admin and outside of the Admin.
for Admin in Django 1.0 and up, you'd need to use
prepopulated_fields = {'slug': ('title',), }
in your admin.py
Your key in the prepopulated_fields dictionary is the field you want filled, and the value is a tuple of fields you want concatenated.
Outside of admin, you can use the slugify function in your views. In templates, you can use the |slugify filter.
There is also this package which will take care of this automatically: https://pypi.python.org/pypi/django-autoslug
Thought I would add a complete and up-to-date answer with gotchas mentioned:
1. Auto-populate forms in Django Admin
If you are only concerned about adding and updating data in the admin, you could simply use the prepopulated_fields attribute
class ArticleAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("title",)}
admin.site.register(Article, ArticleAdmin)
2. Auto-populate custom forms in templates
If you have built your own server-rendered interface with forms, you could auto-populate the fields by using either the |slugify tamplate filter or the slugify utility when saving the form (is_valid).
3. Auto-populating slugfields at model-level with django-autoslug
The above solutions will only auto-populate the slugfield (or any field) when data is manipulated through those interfaces (the admin or a custom form). If you have an API, management commands or anything else that also manipulates the data you need to drop down to model-level.
django-autoslug provides the AutoSlugField-fields which extends SlugField and allows you to set which field it should slugify neatly:
class Article(Model):
title = CharField(max_length=200)
slug = AutoSlugField(populate_from='title')
The field uses pre_save and post_save signals to achieve its functionality so please see the gotcha text at the bottom of this answer.
4. Auto-populating slugfields at model-level by overriding save()
The last option is to implement this yourself, which involves overriding the default save() method:
class Article(Model):
title = CharField(max_length=200)
slug = SlugField()
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super().save(*args, **kwargs)
NOTE: Bulk-updates will bypass your code (including signals)
This is a common miss-understanding by beginners to Django. First you should know that the pre_save and post_save signals are directly related to the save()-method. Secondly the different ways to do bulk-updates in Django all circumvent the save()-method to achieve high performance, by operating directly on the SQL-layer. This means that for the example model used in solution 3 or 4 above:
Article.objects.all().update(title='New post') will NOT update the slug of any article
Using bulk_create or bulk_update on the Article model will NOT update the slug of any article.
Since the save()-method is not called, no pre_save or post_save signals will be emitted.
To do bulk updates and still utilize code-level constraints the only solution is to iterate objects one by one and call its save()-method, which has drastically less performance than SQL-level bulk operations. You could of course use triggers in your database, though that is a totally different topic.
Outside the admin, see this django snippet. Put it in your .save(), and it'll work with objects created programmatically. Inside the admin, as the others have said, use prepopulated_fields.
For pre-1.0:
slug = models.SlugField(prepopulate_from=('title',))
should work just fine
For 1.0, use camflan's
You can also use pre_save django signal to populate slug outside of django admin code.
See Django signals documentation.
Ajax slug uniqueness validation will be useful too, see As-You-Type Slug Uniqueness Validation # Irrational Exuberance
Auto-populating slugfields at model-level with the built-in django slugify:
models.py
from django.db import models
class Place:
name = models.CharField(max_length=50)
slug_name = models.SlugField(max_length=50)
signals.py
from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.template.defaultfilters import slugify as django_slugify
from v1 import models
#receiver(pre_save, sender=models.Place)
def validate_slug_name(sender, instance: models.Place, **kwargs):
instance.slug_name = django_slugify(instance.name)
Credits to: https://github.com/justinmayer/django-autoslug/blob/9e3992296544a4fd7417a833a9866112021daa82/autoslug/utils.py#L18
prepopulated_fields = {'slug': ('title',), }
autoslug has worked quite well for me in the past. Although I've never tried using it with the admin app.
Related
I have a model that contains multiple many to many fields:
class Author(models.Model):
name = models.CharField(max_length=30)
class Topic(models.Model):
description = models.CharField(max_length=30)
class Article(models.Model):
authors = models.ManyToManyField(Author, related_name='articles')
topics = models.ManyToManyField(Topic, related_name='articles')
I need something pretty simple:
A method to be executed after the article is saved where I can access both authors and topics of that instance.
My first attempt has been with a post_save signal, but the signal is fired when the model itself is saved, not after its relationships are saved, which come from a through model obviously.
After some online reading I realized that I probably need to create my own signal and connect to it. The problem is that I have no idea what to overwrite and where to fire that signal.
Since I need this on multiple models, I thought I could create some M2MPostSaveModel class and have my models inherit it so I can just catch the signals..
But where does Django sends the signals? How can I overwrite it? I honestly have no idea and I had no luck searching in the docs, so apologies if it was there already and I didn't see it.
I finally did it, here's how:
I created a new signal
import django.dispatch
m2m_post_save = django.dispatch.Signal(providing_args=["instance"])
Then I created a subclass of the ModelAdmin class that fires it after it finishes saving the related elements:
class M2MPostSaveModelAdmin(ModelAdmin):
def save_related(self, request, form, formsets, change):
super(M2MPostSaveModelAdmin, self).save_related(request, form, formsets, change)
m2m_post_save.send(sender=self.__class__, instance=form.instance)
Now all I have to do is to subclass my ModelAdmin class with my M2MPostSaveModelAdmin class and hook up the signal to my method.
def doo_something_with_updated_instance(instance, **kwargs):
# Here your instance has all the m2m relationships updated
m2m_post_save.connect(doo_something_with_updated_instance sender=YourModelAdminClass)
I used m2m_changed for separate processing.
def topics_changed(sender, instance, **kwargs):
# Do something
m2m_changed.connect(topics_changed, sender=Article.topics.through)
so do with authors also
Django 1.4
Sorry if this is a silly question i am fairly new to Django.
I am attempting to link a user and a profile together via the inbuilt auth profile system. All the examples of this i can find do not use a class based view, which is something i would really like to use.
Basically i would like a form that combines the Profile and the User allowing me to create both at the same time. If possible i would like to use the same form to Edit/Create the User + Profile.
I have created a model for the profile: Profile
Created forms:
class UserForm(forms.ModelForm):
class Meta:
model = User
class ProfileRegisterView(FormView):
template_name = 'profile-register-form.html'
form_class = UserForm
success_url = '/account/created/'
Adding the profile to the user model does not seem to include it within the UserForm:
AUTH_PROFILE_MODULE = "creative_profile.Profile"
The 2nd alternative i have tried was to define individual forms in forms.py however the form_class attribute only accepts one form model..
Any pointers help would be great, thanks
One possible solution is to include the Profile fields in your UserForm and override the save() method to populate the Profile fields.
The save() method will have to include a get_or_create() call for the Profile model if you're not using a post_save signal to create it. If you are using a post_save signal to create the Profile model, you're going to have to make sure the User is being saved first before calling the get_profile() method.
I do it in more simple way (i suggest). Just use django build in. In urls.py I added (r'^login/$','django.contrib.auth.views.login'). In settings.py add LOGIN_URL='/login/' and to MIDDLEWARE_CLASSES add 'django.contrib.auth.middleware.AuthenticationMiddleware'. Copy registration/login.html template locally if you want to change it. After such manipulations you will have ability to login as user. Forgot, you also should import from django.contrib.auth.models User and Group.
in django by default when syncdb is run with django.contrib.auth installed, it creates default permissions on each model... like foo.can_change , foo.can_delete and foo.can_add. To add custom permissions to models one can add class Meta: under the model and define permissions there, as explained here https://docs.djangoproject.com/en/4.1/topics/auth/customizing/#custom-permissions
My question is that what should I do if I want to add a custom permission to the User model? like foo.can_view. I could do this with the following snippet,
ct = ContentType.objects.get(app_label='auth', model='user')
perm = Permission.objects.create(codename='can_view', name='Can View Users',
content_type=ct)
perm.save()
But I want something that plays nicely with syncdb, for example the class Meta under my custom models. Should I just have these in class Meta: under UserProfile since that is the way to extend the user model. but is that the RIGHT way to do it? Wouldn't that tie it to UserProfile model?
You could do something like this:
in the __init__.py of your Django app add:
from django.db.models.signals import post_syncdb
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth import models as auth_models
from django.contrib.auth.models import Permission
# custom user related permissions
def add_user_permissions(sender, **kwargs):
ct = ContentType.objects.get(app_label='auth', model='user')
perm, created = Permission.objects.get_or_create(codename='can_view', name='Can View Users', content_type=ct)
post_syncdb.connect(add_user_permissions, sender=auth_models)
I don't think there is a "right" answer here, but i used the exact same code as you except i changed Permission.objects.create to Permission.objects.get_or_create and that worked find to sync with syncdb
An updated answer for Django 1.8. The signal pre_migrate is used instead of pre_syncdb, since syncdb is deprecated and the docs recommend using pre_migrate instead of post_migrate if the signal will alter the database. Also, #receiver is used to connect add_user_permissions to the signal.
from django.db.models.signals import pre_migrate
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth import models as auth_models
from django.contrib.auth.models import Permission
from django.conf import settings
from django.dispatch import receiver
# custom user related permissions
#receiver(pre_migrate, sender=auth_models)
def add_user_permissions(sender, **kwargs):
content_type = ContentType.objects.get_for_model(settings.AUTH_USER_MODEL)
Permission.objects.get_or_create(codename='view_user', name='View user', content_type=content_type)
This is a bit hacky but mentioning it here anyway for reference.
My site has a generic model called Setting, which stores various settings concerning the site I want certain users to be able to edit, without needing to go through me the developer (like registration limit, or an address, or the cost of items, etc).
All the permissions that don't nicely map onto other models (eg "Send Password Reminder Email to Student", "Generate Payment Reconciliation Report", "Generate PDF Receipt"), which really just relate to pages that get viewed in the admin area, get dumped onto this Setting model.
For example, here's the model:
class Setting(models.Model):
name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(editable=False)
description = models.TextField()
value = models.TextField()
class Meta:
#for permissions that don't really relate to a particular model, and I don't want to programmatically create them.
permissions = (
("password_reminder", "Send Password Reminder"),
("generate_payment_reconciliation_report", "Generate Payment Reconciliation Report"),
("generate_pdf_receipt", "Generate PDF Receipt"),
)
Do each of those settings strictly relate to the Setting model? No, which is why I said this is a bit hacky. But it is nice that I can now just dump all those permissions here, and Django's migration commands will take care of the rest.
So I've set up my django site with the following admin.py:
import models
from django.contrib import admin
admin.site.register(models.Comment)
which uses this models.py:
from django.db import models
class Comment(models.Model):
text = models.CharField(max_length=400)
name = models.CharField(max_length=100)
date = models.DateTimeField(auto_now = True)
article = models.CharField(max_length=100)
However, when I go to the admin page, it shows the following:
Which generally is not very helpful. Clicking on each link gives me a page with that object's data, but I would like to be able to see the information for each object in this view. I've been looking at the ModelAdmin class at:
https://docs.djangoproject.com/en/dev/ref/contrib/admin/
but have not managed to wrap my head around it. Is it a separate model class that needs to be kept in sync with my "actual" model? Is it just an interface through which my Admin site accesses the actual model? Does it do what I want (allowing useful data to be shown in the admin interface) or does it do something else?
I'm thinking that the Django Admin page should be able to replace PHPMyAdmin for doing simple tasks, like browsing the DB and manually modifying individual objects. Is that the case?
The admin turns your object into a string so just put a def __str__ or def __unicode__
(As #Mandax has reminded me the docs recommend to define __unicode__ only.)
def __unicode__(self);
return u"%s (%s): %s" % (self.article, self.date, self.name)
Just as it says in the documentation, your model's ModelAdmin describes how the admin section will represent your model. It does need to be somewhat in sync with the actual model, it doesn't make sense for you to display fields that aren't present on your model, etc. You seem interested in the changelist view, which has numerous customization options (all described in the documentation, and in the tutorial). A simple start might be:
from django.contrib import admin
class CommentAdmin(admin.ModelAdmin):
# define which columns displayed in changelist
list_display = ('text', 'name', 'date', 'article')
# add filtering by date
list_filter = ('date',)
# add search field
search_fields = ['text', 'article']
admin.site.register(Comment, CommentAdmin)
There are a lot of options for customization, as always refer to the docs! Finally, you could certainly use it in lieu of PHPMyAdmin, it's very easy to setup admin for browsing, modifying object, etc, how much use you get out of it is up to you.
In my django site I have two apps, blog and links. blog has a model blogpost, and links has a model link. There should be a one to many relationship between these two things. There are many links per blogpost, but each link has one and only one blog post. The simple answer is to put a ForeignKey to blogpost in the link model.
That's all well and good, however there is a problem. I want to make the links app reusable. I don't want it to depend upon the blog app. I want to be able to use it again in other sites and perhaps associate links with other non-blogpost apps and models.
A generic foreign key seems like it might be the answer, but not really. I don't want links to be able to associate with any model in my site. Just the one that I explicitly specify. And I know from prior experience that there can be issues using generic foreign keys in terms of database usage because you can't do a select_related over a generic foreign key the way you can with a regular foreign key.
What is the "correct" way to model this relationship?
If you think the link app will always point to a single app then one approach would be to pass the name of the foreign model as a string containing the application label instead of a class reference (Django docs explanation).
In other words, instead of:
class Link(models.Model):
blog_post = models.ForeignKey(BlogPost)
do:
from django.conf import setings
class Link(models.Model):
link_model = models.ForeignKey(settings.LINK_MODEL)
and in your settings.py:
LINK_MODEL = 'someproject.somemodel'
I think TokenMacGuy is on the right track. I would look at how django-tagging handles a similar generic relationship using the content type, generic object_id, and generic.py. From models.py
class TaggedItem(models.Model):
"""
Holds the relationship between a tag and the item being tagged.
"""
tag = models.ForeignKey(Tag, verbose_name=_('tag'), related_name='items')
content_type = models.ForeignKey(ContentType, verbose_name=_('content type'))
object_id = models.PositiveIntegerField(_('object id'), db_index=True)
object = generic.GenericForeignKey('content_type', 'object_id')
objects = TaggedItemManager()
class Meta:
# Enforce unique tag association per object
unique_together = (('tag', 'content_type', 'object_id'),)
verbose_name = _('tagged item')
verbose_name_plural = _('tagged items')
Anoher way to solve this is how django-mptt does this: define only an abstract model in a reusable app(MPTTModel), and require to inherit it with defining some fields (parent=ForeignKey to self, or whatever your app usecase will require)
Probably you need to use the content types app to link to a model. You might then arrange for your app to check the settings to do some additional checking to limit which content types it will accept or suggest.
I'd go with generic relations. You can do something like select_related, it just require some extra work. But I think it's worth it.
One possible solution for generic select_related-like functionality:
http://bitbucket.org/kmike/django-generic-images/src/tip/generic_utils/managers.py
(look at GenericInjector manager and it's inject_to method)
This question and Van Gale's answer lead me to the question, how it could be possible, to limit contenttypes for GFK without the need of defining it via Q objects in the model, so it could be completly reuseable
the solution is based on
django.db.models.get_model
and the eval built-in, that evaluates a Q-Object from settings.TAGGING_ALLOWED. This is necessary for usage in the admin-interface
My code is quite rough and not fully tested
settings.py
TAGGING_ALLOWED=('myapp.modela', 'myapp.modelb')
models.py:
from django.db import models
from django.db.models import Q
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from django.db.models import get_model
from django.conf import settings as s
from django.db import IntegrityError
TAGABLE = [get_model(i.split('.')[0],i.split('.')[1])
for i in s.TAGGING_ALLOWED if type(i) is type('')]
print TAGABLE
TAGABLE_Q = eval( '|'.join(
["Q(name='%s', app_label='%s')"%(
i.split('.')[1],i.split('.')[0]) for i in s.TAGGING_ALLOWED
]
))
class TaggedItem(models.Model):
content_type = models.ForeignKey(ContentType,
limit_choices_to = TAGABLE_Q)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
def save(self, force_insert=False, force_update=False):
if self.content_object and not type(
self.content_object) in TAGABLE:
raise IntegrityError(
'ContentType %s not allowed'%(
type(kwargs['instance'].content_object)))
super(TaggedItem,self).save(force_insert, force_update)
from django.db.models.signals import post_init
def post_init_action(sender, **kwargs):
if kwargs['instance'].content_object and not type(
kwargs['instance'].content_object) in TAGABLE:
raise IntegrityError(
'ContentType %s not allowed'%(
type(kwargs['instance'].content_object)))
post_init.connect(post_init_action, sender= TaggedItem)
Of course the limitations of the contenttype-framework affect this solution
# This will fail
>>> TaggedItem.objects.filter(content_object=a)
# This will also fail
>>> TaggedItem.objects.get(content_object=a)