Suppose we have this django models:
from django.db import models
# Create your models here.
class Artist(models.Model):
name = models.CharField(max_length=10)
class Album(models.Model):
name = models.CharField(max_length=10)
date = models.DateField()
artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
So I can write:
artist_one = models.Artist.objects.create(name='Santana')
album_one = models.Album.objects.create(name='Abraxas', date = datetime.date.today(), artist=artist_one)
album_two = models.Album.objects.create(name='Supernatural', date = datetime.date.today(), artist=artist_one)
How can I add a constrain to the Album class as to say an artist cannot publish two albums the same year?
You could override the model's save method like this.
def save(self, *args, **kwargs):
year = self.date.year
albums_of_year = Album.objects.filter(artist=self.artist, date__year=year)
if albums_of_year:
# you can raise your error here
else:
super(Album, self).save(*args, **kwargs)
Use validators on model
from django.core.exceptions import ValidationError
def validate_atrist_album(value):
#logic to check if artist has album from same year here
if not user_can_publish:
raise ValidationError('Artist can't publish')
class Album(models.Model):
name = models.CharField(max_length=10)
date = models.DateField()
artist = models.ForeignKey(
Artist,
on_delete=models.CASCADE,
validators=[
validate_artist_album,
],
)
Related
I need to join and pass two queries of category and subcategory as elements of an autocomplete select2 dropdown as my django form field as below:
This is my form:
class CategoriesAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
if not self.request.user.is_authenticated:
return Categories.objects.none()
qs = Categories.objects.all()
if self.q:
qs = qs.filter(Q(name__icontains=self.q))
return qs
def get_result_label(self, item):
return format_html( item.name)
class categories_form(forms.ModelForm):
categories = forms.ModelChoiceField(
queryset= Categories.objects.none(),
widget= autocomplete.ModelSelect2(
url='load_categories',
attrs={
'data-placeholder': 'Select a category',
'data-html': True,
'style': 'min-width: 15em !important;',
}
)
)
class Meta:
model = Post
fields = ['categories']
def __init__(self, *args, **kwargs):
super(category_form, self).__init__(*args, **kwargs)
self.fields['categories'].queryset = Categories.objects.all()
and in url:
path('ajax/categories-autocomplete', CategoriesAutocomplete.as_view(), name='load_categories'),
for category model:
class Categories(models.Model):
name = models.CharField(max_length=100)
brief = models.TextField(null=True)
slug = models.SlugField(max_length=200)
date = models.DateTimeField(default=timezone.now)
class Meta:
db_table = "categories"
for sub-category model:
class Sub_Categories(models.Model):
name = models.CharField(max_length=100)
brief = models.TextField(null=True)
slug = models.SlugField(max_length=200)
date = models.DateTimeField(default=timezone.now)
class Meta:
db_table = "sub_categories"
model to connect category with sub-categories:
class Categories_Sub_Categories(models.Model):
category = models.OneToOneField(Categories, primary_key=True, on_delete=models.CASCADE)
sub_cat = models.OneToOneField(Sub_Categories, on_delete=models.CASCADE)
class Meta:
db_table = "categories_sub-categories"
and in Post model:
class Post(models.Model):
title = models.CharField(max_length=50)
descript = HTMLField(blank=True, null=True)
categories = models.ForeignKey(Categories, blank=True, null=True, on_delete=models.CASCADE)
subcategories = models.ForeignKey(Sub_Categories, blank=True, null=True, on_delete=models.CASCADE)
author = models.ForeignKey(User, on_delete=models.CASCADE)
date = models.DateTimeField(default=timezone.now)
UPDATE:
The answer provided by #ha-neul is working just there is a bug. I show it with an example:
This is what expect in the dropdown:
**Asia**
China
Malaysia
India
Tajikistan
Iran
Qatar
**Europe**
Germany
Italy
Spain
Netherlands
France
**Africa**
Gana
...
But this is what I see:
**Asia**
China
Malaysia
**Europe**
Netherlands
France
Sweden
Norway
**Asia**
India
Tajikistan
Iran
**Europe**
Germany
Italy
**Asia**
Qatar
**Africa**
Gana
...
**America**
....
**Europe**
Spain
in the SubCategory table I have something like:
id ........... category_id
1 1
2 1
3 1
4 1
5 3
6 1
7 2
I am following this package. Any idea to make me even closer to the solution would be appreciated!!
If this is what you want to achieve, then:
The short answer is you should
have your subcategory with ForeignKeyField referring to
Category model.
use Select2GroupQuerySetView instead of Select2QuerySetView.
But implementing it is a bit complicated.
First of all, although django-autocomplete-light's source code has Select2GroupQuerySetView , somehow you cannot just use it as autocomplete.Select2GroupQuerySetView. So, you have to write the same thing in your own views.py. In addition, the source code has a typo, so you need to fix it.
Step 1. In models.py:
class Category(models.Model):
name = models.CharField(max_length=100)
brief = models.TextField(null=True)
slug = models.SlugField(max_length=200)
date = models.DateTimeField(default=timezone.now)
class SubCategory(models.Model):
##################
#You need add this line, so there is a one-to-many relationship
category = models.ForeignKey(Category, on_delete=models.CASCADE,
related_name='subcategories')
###############
name = models.CharField(max_length=100)
brief = models.TextField(null=True)
slug = models.SlugField(max_length=200)
date = models.DateTimeField(default=timezone.now)
Step2.in views.py copy-paste Select2GroupQuerySetView code and fix a typo
# import collections, so Select2GroupQuerySetView can work
import collections
class Select2GroupQuerySetView(autocomplete.Select2QuerySetView):
group_by_related = None
related_field_name = 'name'
def get_results(self, context):
if not self.group_by_related:
raise ImproperlyConfigured("Missing group_by_related.")
groups = collections.OrderedDict()
object_list = context['object_list']
print(object_list)
object_list = object_list.annotate(
group_name=F(f'{self.group_by_related}__{self.related_field_name}'))
for result in object_list:
group_name = getattr(result, 'group_name')
groups.setdefault(group_name, [])
groups[group_name].append(result)
return [{
'id': None,
'text': group,
'children': [{
'id': result.id,
'text': getattr(result, self.related_field_name),
# this is the line I had to comment out
#'title': result.descricao
} for result in results]
} for group, results in groups.items()]
3. write your own view using Select2GroupQuerySetView
class SubCategoryAutocomplete(Select2GroupQuerySetView):
print('under subcategory autocomplete')
group_by_related = 'category' # this is the fieldname of ForeignKey
related_field_name = 'name' # this is the fieldname that you want to show.
def get_queryset(self):
##### Here is what you normally put... I am showing the minimum code.
qs = SubCategory.objects.all()
if self.q:
qs = qs.filter(name__istartswith=self.q)
return qs
Howe to use this view in a your project?
1. Say you have a Post model as below, with subcategory as a ForeignKey.
class Post(models.Model):
title = models.CharField(max_length=100)
subcategory = models.ForeignKey(SubCategory,on_delete=models.CASCADE)
def __str__(self):
return self.title
2. You will generate a PostForm that contains the subcategory autocomplete field.
in forms.py
from django.conf.urls import url
class PostForm(forms.ModelForm):
subcategory = forms.ModelChoiceField(
queryset=Subcategory.objects.all(),
widget=autocomplete.ModelSelect2(url='subcategory-autocomplete')
)
class Meta:
model= Post
fields = ('title','subcategory')
You will generate a CreatePostView using generic CreateView
from django.views.generic.edit import CreateView
class CreatePostView(CreateView):
model=Post
template_name='yourapp/yourtemplate.html'# need to change
form_class=PostForm
success_url = '/'
Now, in your urls.py, one url for CreatePostView another one for autocomplete view.
urlpatterns = [
url(
r'^subcategory-autocomplete/$',
SubCategoryAutocomplete.as_view(),
name='subcategory-autocomplete',
),
path('post/create',CreatePostView.as_view(), name='create_post'),
it's all set, you will go to post/create and see a PostForm with subcategories autocomplete field.
OP had a weird grouping behavior after using the code above. In his comment, he mentioned:
Added a .order_by(category_id')` to the qs and fixed it.
You do not need to create this model to make a relation
Categories_Sub_Categories
just create a one-to-many field (categories) in Sub_Categories model and put Categories model there (foreign), it will do that automatically, then retrieve data like this (in backend)
categories = Categories.objects.all()
you will get all categories with Sub_Categories object here, pass it to frontend and loop through it (in front-end)
for category in categories:
sub_categories = category.sub_categories_set.all()
To make SubCategory you can have ForeignKey to self.
Another point, you'll need to use prefetch_related from main model (Post) to be able to "join" Category/SubCategory there.
Here is an example how this should look like:
# forms.py
from django import forms
from django.db.models import Prefetch
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = [...]
def __init__(self, *args, **kwargs):
super(PostForm, self).__init__(*args, **kwargs)
cats = Category.objects \
.filter(category__isnull=True) \
.order_by('order') \
.prefetch_related(Prefetch('subcategories',
queryset=Category.objects.order_by('order')))
self.fields['subcategory'].choices = \
[("", self.fields['subcategory'].empty_label)] \
+ [(c.name, [
(self.fields['subcategory'].prepare_value(sc),
self.fields['subcategory'].label_from_instance(sc))
for sc in c.subcategories.all()
]) for c in cats]
# models.py
class Category(models.Model):
category = models.ForeignKey('self', null=True, on_delete=models.CASCADE,
related_name='subcategories', related_query_name='subcategory')
class Post(models.Model):
subcategory = models.ForeignKey(Category, on_delete=models.CASCADE,
related_name='posts', related_query_name='post')
my question is i want to show only particular titles under music_track (musicmodel)field when type = track(title model) in my django admin site
class album(models.Model):
def get_autogenerated_code():
last_id = album.objects.values('id').order_by('id').last()
if not last_id:
return "AL-"+str(0)
return "AL-"+str(last_id['id'])
album_name = models.CharField( max_length=150, blank=False )
music_track = models.ManyToManyField("title")
def __str__(self):
return (self.album_name)
class Meta:
verbose_name = "Album"
verbose_name_plural = "Albums"
class title(models.Model):
def get_autogenerated_code():
last_id = title.objects.values('id').order_by('id').last()
if not last_id:
return "TT-"+str(0)
return "TT-"+str(last_id['id'])
upc_code = models.CharField(max_length=15, default="N/A", blank=False)
display_name = models.CharField(max_length=150, blank=False)
type = models.ForeignKey(Asset_Type, on_delete=models.CASCADE, null=True)
def __str__(self):
return (self.display_name+ " " +self.code)
admin.site.register( [album, title] )
From your question, I am understanding that while creating an album in your admin panel, you require that the music_track should only show the titles having type as track. My solution for this is:
In your admin.py file
from .models import title, album, Asset_type
class AlbumForm(forms.ModelForm):
class Meta:
model = Product
fields = ('album_name', 'music_track', )
def __init__(self, user, *args, **kwargs):
super(AlbumForm, self).__init__(*args, **kwargs)
type = Asset_type.objects.get(type='track')
self.fields['music_track'].queryset = Title.objects.filter(type=type)
class MyModelAdmin(admin.ModelAdmin):
form = AlbumForm
admin.site.register(album, MyModelAdmin)
Maybe this can give you the idea you need.
I am a newbie in Django. I have 3 models: Continent, Country, Region
Here is the code:
from django.db import models
# Create your models here.
class Continent(models.Model):
continent = models.CharField(max_length=50, unique=True)
class Meta:
ordering = ['continent']
def __str__(self):
return self.continent
class Country(models.Model):
country = models.CharField(max_length=50, unique=True)
continent = models.ForeignKey(Continent)
class Meta:
ordering = ['country']
verbose_name_plural = 'Countries'
def __str__(self):
return self.country
class Region(models.Model):
country = models.ForeignKey(Country)
region = models.CharField(max_length=50)
class Meta:
ordering = ['region']
def __str__(self):
return self.region
def get_continent(self):
return self.get_continent()
my admin.py looks like this:
from django.contrib import admin
from location.models import Continent, Country, Region
# Register your models here.
class MyAdmin1(admin.ModelAdmin):
list_display = ['continent']
#list_display_links = None
#actions = None
class MyAdmin2(admin.ModelAdmin):
list_display = ['country', 'continent']
class MyAdmin3(admin.ModelAdmin):
model = Region
list_display = ['region', 'country', 'get_continent']
admin.site.register(Continent, MyAdmin1)
admin.site.register(Country, MyAdmin2)
admin.site.register(Region, MyAdmin3)
But in admin panel when I click on table regions it doesn't show 3 attributes in 3 columns. Please, help.
You get a infinite recursion in the Region.get_continent() method:
class Region(models.Model):
...
def get_continent(self):
return self.get_continent()
Change it to:
def get_continent(self):
return self.country.continent
I really need somebody to explain/show me how I can achieve a TabularInline display in the django admin console of my example. Could somebody help me out?
My models are as follows:
from django.db import models
class Player(models.Model):
player_id = models.IntegerField(primary_key=True)
team = models.ForeignKey(Team)
player_name = models.CharField(max_length=140)
position = models.CharField(max_length=10)
def __str__(self):
return '%s' % (self.player_name)
class MatchdayStats(models.Model):
MATCHDAY_STATS_ID = models.AutoField(primary_key=True)
appeared = models.BooleanField(default=False)
goal = models.IntegerField(default=0)
minutes_under_60 = models.BooleanField(default=False)
minutes_60 = models.BooleanField(default=False)
assist = models.IntegerField(default=0)
def __str__(self):
return '%s' % (self.MATCHDAY_STATS_ID)
class PlayerGameweekStats(models.Model):
PLAYER_GAMEWEEK_ALLSTATS_ID = models.AutoField(primary_key=True)
player = models.ForeignKey(Player)
gameweek = models.ForeignKey('fixturesresults.Gameweek')
matchday_stats = models.ForeignKey(MatchdayStats)
def __str__(self):
return '%s (gw=%s,msid=%s)' % (self.player.player_name,self.gameweek.GAMEWEEK_ID,self.matchday_stats.MATCHDAY_STATS_ID)
I would like there to be a tabular display for the PlayerGameweekStats model, where you can enter MatchdayStats fields for each player.
The admin code below causes a Foreign Key error <class 'playerteamstats.models.MatchdayStats'> has no ForeignKey to <class 'playerteamstats.models.PlayerGameweekStats'>
class StatsInLine(admin.TabularInline):
model = MatchdayStats
class PlayerGameweekStatsAdmin(admin.ModelAdmin):
list_display = ('player', 'gameweek')
exclude = ('gameweek')
inlines = [
StatsInLine,
]
admin.site.register(PlayerGameweekStats, PlayerGameweekStatsAdmin)
To build TabularInline models need to be connected with ForeignKey.
From Django docs example:
models.py:
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
author = models.ForeignKey(Author)
title = models.CharField(max_length=100)
admin.py:
from django.contrib import admin
class BookInline(admin.TabularInline):
model = Book
class AuthorAdmin(admin.ModelAdmin):
inlines = [
BookInline,
]
In you case you need to have ForeignKey to PlayerGameweekStats in MatchdayStats.
Is it possible to have a field in a Django model which does not get stored in the database.
For example:
class Book(models.Model):
title = models.CharField(max_length=75)
description models.CharField(max_length=255, blank=True)
pages = models.IntegerField()
none_db_field = ????
I could then do
book = Book.objects.get(pk=1)
book.none_db_field = 'some text...'
print book.none_db_field
Thanks
As long as you do not want the property to persist, I don't see why you can't create a property like you described. I actually do the same thing on certain models to determine which are editable.
class Email(EntryObj):
ts = models.DateTimeField(auto_now_add=True)
body = models.TextField(blank=True)
user = models.ForeignKey(User, blank=True, null=True)
editable = False
...
class Note(EntryObj):
ts = models.DateTimeField(auto_now_add=True)
note = models.TextField(blank=True)
user = models.ForeignKey(User, blank=True, null=True)
editable = True
Creating a property on the model will do this, but you won't be able to query on it.
Example:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
def _get_full_name(self):
return "%s %s" % (self.first_name, self.last_name)
def _set_full_name(self, combined_name):
self.first_name, self.last_name = combined_name.split(' ', 1)
full_name = property(_get_full_name)
full_name_2 = property(_get_full_name, _set_full_name)
Usage:
from mysite.models import Person
a = Person(first_name='John', last_name='Lennon')
a.save()
a.full_name
'John Lennon'
# The "full_name" property hasn't provided a "set" method.
a.full_name = 'Paul McCartney'
Traceback (most recent call last):
...
AttributeError: can't set attribute
# But "full_name_2" has, and it can be used to initialise the class.
a2 = Person(full_name_2 = 'Paul McCartney')
a2.save()
a2.first_name
'Paul'
To make it an instance variable (so each instance gets its own copy), you'll want to do this
class Book(models.Model):
title = models.CharField(max_length=75)
#etc
def __init__(self, *args, **kwargs):
super(Foo, self).__init__(*args, **kwargs)
self.editable = False
Each Book will now have an editable that wont be persisted to the database
If you want i18n support:
# Created by BaiJiFeiLong#gmail.com at 2022/5/2
from typing import Optional
from django.db import models
from django.utils.translation import gettext_lazy as _
class Blog(models.Model):
title = models.CharField(max_length=128, unique=True, verbose_name=_("Title"))
content = models.TextField(verbose_name=_("Content"))
_visitors: Optional[int] = None
#property
def visitors(self):
return self._visitors
#visitors.setter
def visitors(self, value):
self._visitors = value
visitors.fget.short_description = _("Visitors")