DJANGO: AUTO ADD THE RESPECTIVE FOREIGN KEY VALUE IN CREATE VIEW - django

I am creating a CreateView with the following models:
from django.db import models
from uuid import uuid4
from django.core.validators import MinValueValidator,MaxValueValidator
from questions.models import Question
from django.urls import reverse
class ExamPaper(models.Model):
id = models.UUIDField(primary_key=True, default=uuid4)
name = models.CharField(max_length=300)
for_class = models.PositiveSmallIntegerField(
validators=(
MinValueValidator(1),
MaxValueValidator(12),
))
date = models.DateField(null=True, blank=True)
PARTS = (
("A", "A"),
("B", "B"),
("C", "C"),
("D", "D"),
("E", "E"),
)
part = models.CharField(
max_length=1, choices=PARTS, null=True,blank=True)
def __str__(self):
return f"{self.name} ({self.get_part_display()})"
def get_absolute_url(self):
return reverse("exam_detail", args=[str(self.pk)])
class ExamQuestions(models.Model):
exam = models.ForeignKey(
ExamPaper, on_delete=models.SET_NULL, null=True,
related_name="examquestions")
question = models.ForeignKey(
Question, on_delete=models.CASCADE,)
marks = models.PositiveSmallIntegerField(
validators=(MaxValueValidator(20), ))
def __str__(self):
return self.question.question[:150]
def get_absolute_url(self):
return reverse("exam_detail", args=[str(self.exam.pk)])
I am trying to make a form using CreateView in which I can add questions and marks to a ExamPaper. I have allready created a CreatView using ExamPaper model and added a link in it to add questions.
What i want is that whenever I add questions the exam field gets assigned to that question.
views.py
class ExamCreateView(CreateView):
model = ExamPaper
template_name = "exam_new.html"
fields = "__all__"
class ExamQuestionsView(CreateView):
model = ExamQuestions
fields = "question", "marks",
template_name = "exam_ques_new.html"
I am able to get a form for both but in ExamQuestionView I have to select exam from the whole list.

Related

Django : change the id with another field in the views page

How do I tell to Django to replace the Column type_id to the name field in the views (html page).
and here I have foreignkey, it gave me id (type_id), and this screentshot of fabrication class:
the column type_id is comming from the composant_type class,
models.py:
from django.db import models
from django.contrib.auth.models import User
from django.db.models.base import Model
from CentreCout.models import CentreCoutDB
class fiche(models.Model):
centre_cout = models.CharField(max_length=150)
number = models.CharField(max_length=100)
name = models.CharField(max_length=100, unique=True)
def __str__(self):
return self.name
class unite(models.Model):
name = models.CharField(max_length= 150, unique=True)
def __str__(self):
return self.name
class composant_type(models.Model):
name = models.CharField(max_length=150, unique=True )
def __str__(self):
return f"({self.name})"
class composant_fab(models.Model):
type = models.ForeignKey(composant_type, to_field='name', on_delete=models.CASCADE)
name = models.CharField(max_length=150, unique=True)
def __str__(self):
return f"({self.name})"
class fabrication(models.Model):
grade = models.ForeignKey(fiche, to_field='name',on_delete=models.CASCADE)
type = models.ForeignKey(composant_type, on_delete=models.CASCADE, blank=True, null=True)
composant = models.ForeignKey(composant_fab , to_field='name', on_delete=models.CASCADE, null=True, blank=True)
unite = models.ForeignKey(unite, to_field='name',on_delete=models.CASCADE)
composant_value = models.FloatField(blank=True, null=True)
def __str__(self):
return f"({self.grade}-{self.composant}-{self.composant_value}-{self.unite})"
views.py
from django.shortcuts import render
from django import views
from django.http import HttpResponse
from .models import *
import pandas as pd
def fabrications(request):
lesfichestab = fiche.objects.all()
fabricationtab = fabrication.objects.all().values()
df = pd.DataFrame(fabricationtab)
context = {
'lesfichestab':lesfichestab,
'fabricationtab':df.to_html()
}
return render(request,'fabrications/fabricationpage.html', context)
Note: I use Pandas method, because i have to do some Filtering and pivoting of the table.
I get an answer from here :
Django with Pandas accessing ForeignKey
admin_data = pd.DataFrame.from_records(
administrations.values(
"id",
"study__name", # <-- get the name through the foreign key
"url_hash",
)
).rename(
columns={
"id": "administration_id",
"study__name": "study_name",
"url_hash": "link",
}
)

constrains on django model with ForeignKey

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,
],
)

Django model select between two columns/fields?

I have a Slider module that i want to include items from movies_movie and shows_show table. An item can either be a show or movie. How do i make user select between movie and show? Currently i have columns for movie and show but how do i force user to select between the two?
also title_en is a column in movie or tv show tables. So the title of the movie/show selected should display in row after save.
class Slider_items(models.Model):
order = models.IntegerField(max_length=3, blank=True)
movie = models.ForeignKey('movies.movie', on_delete=models.CASCADE, blank=True)
show = models.ForeignKey('shows.show', on_delete=models.CASCADE, blank=True)
def __str__(self):
return self.title_en
class Meta:
verbose_name = "Slider Items Module"
verbose_name_plural = "Slider Item Module"
Also if a show is selected and a movie isn't, how do i know title_en will be taken from show and not movie?
I think you can do something like this:
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
class Slider_items(models.Model):
order = models.IntegerField(max_length=3, blank=True)
# don't forget to add null=True to both fields
movie = models.ForeignKey('movies.movie', on_delete=models.CASCADE, blank=True, null=True)
show = models.ForeignKey('shows.show', on_delete=models.CASCADE, blank=True, null=True)
# see docs, https://docs.djangoproject.com/en/3.2/ref/models/instances/#django.db.models.Model.clean
def clean(self):
if self.movie and self.show:
raise ValidationError({'movie': _('You can't select both types at the same time')})
elif not self.movie and not self.show:
raise ValidationError({'movie': _('You must select one type')})
def __str__(self):
return self.movie.title_en if self.movie else self.show.title_en
class Meta:
verbose_name = "Slider Items Module"
verbose_name_plural = "Slider Item Module"
You may consider using django contenttypes.
Imagine in the future, you have not just Movie, Show, but have new Class such as Book, Podcase, it might not be a good idea to keep adding new foreignkey to your Slider Model.
I have not used contenttype before, so I am referencing this SO answer.
(using python 3.6, django 3.2)
models.py
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class Movie(models.Model):
title = models.CharField(max_length=50)
director = models.CharField(max_length=50)
class Show(models.Model):
title = models.CharField(max_length=50)
date = models.DateField()
class Slider(models.Model):
order = models.IntegerField(max_length=3, blank=True)
choices = models.Q(model='movie') | models.Q(model='show')
selection_type = models.ForeignKey(
ContentType, limit_choices_to=choices,
on_delete=models.CASCADE)
selection_id = models.PositiveIntegerField()
selection = GenericForeignKey('selection_type', 'selection_id')
def __str__(self):
return self.selection.title
admin.py
#admin.register(Slider)
class SliderAdmin(admin.ModelAdmin):
pass
at django shell, the following is valid.
movie = Movie.objects.create(title='movie 1', director='ben')
show = Show.objects.create(title='show 1', date='2021-01-01')
s1 = Slider.objects.create(selection=movie, order=1)
s2 = Slider.objects.create(selection=show, order=2)
However, using limit_choices_to only restrict the choices in admin page, and there is no constraint at database level. i.e. the following are actually legal.
place = Place.objects.create(name='home')
s3 = Slider.objects.create(selection=s3, order=3)
I have not found a fix for this issue yet. Maybe doing some validation in save method is a way (see the comments under this).

How to mix two queries to one as dropdown elements

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')

How to write query with in a model with foreign key relation?

class Categorie(models.Model):
name = models.CharField(max_length=200)
description = models.TextField(max_length=2000)
status = models.BooleanField(default=True)
added_date = models.DateTimeField(auto_now_add=True)
def __unicode__(self):
return u'%s' % self.name
class GroupQuestion(models.Model):
category = models.ForeignKey(Categorie, related_name='%(class)s_name_related', null=True)
name = models.CharField(max_length=200)
added_date = models.DateTimeField(auto_now_add=True)
status = models.BooleanField(default=True)
class Meta:
verbose_name = 'Group of Questions'
verbose_name_plural = 'Group of Questions'
def __unicode__(self):
return u'%s' % self.name
class Question(models.Model):
category = models.ForeignKey(Categorie, related_name='%(class)s_name_related', null=True)
group = models.ForeignKey(GroupQuestion, related_name='%(class)s_name_related')
name = models.TextField(max_length=200)
added_date = models.DateTimeField(auto_now_add=True)
status = models.BooleanField(default=True)
#select_choice = models.CharField(max_length=5, choices=QUESTION_CHOICES, null=True)
positive_answer = models.PositiveIntegerField()
negative_answer = models.PositiveIntegerField()
def __unicode__(self):
return u'%s' % self.name
I have these three model. When I pass category_id from my API wants to get QuestionGroups and Questions related to that group?
Some thing like this
"data": [
{
id:1
name = ABC
"question":[
{
"id":1
"name":ABC
}
{
"id":1
"name":ABC
}
]
}
{
id:1
name = ABC
"question":[
{
"id":1
"name":ABC
}
{
"id":1
"name":ABC
}
]
}
}
I am new in django some can help me how to write query in view and how to serialize data.
You are looking for a nested serialization.
You should create two serializers. One for your GroupQuestion model and one for your Question model. You will use in your QuestionSerializer inside your GroupQuestionSerializer in order to perform a nested serialization.
In your serializers.py:
from rest_framework import serializers
from .models import GroupQuestion, Question
# Serializer for the Question model
class QuestionSerializer(serializers.ModelSerializer):
class Meta:
model = Question
fields = ('id', 'name')
# Serializer for the GroupQuestion model
class GroupQuestionSerializer(serializers.ModelSerializer):
question_name_related = QuestionSerializer(read_only=True, many=True)
# The magic happens here ^
# You use your QuestionSerializer inside your GroupQuestionSerializer
# In order to serialize the related Questions for this GroupQuestion
class Meta:
model = GroupQuestion
fields = ('id', 'question_name_related')
Then let's say you have a simple ListAPIView for listing your GroupQuestion entries.
In your views.py:
from rest_framework import generics
from .models import GroupQuestion
from .serializers import GroupQuestionSerializer
# Import the serializer that we have just created
class GroupQuestionList(generics.ListAPIView):
serializer_class = GroupQuestionSerializer
def get_queryset(self):
category_id = self.kwargs['category_id']
category = generics.get_object_or_404(Categorie, id=category_id)
return GroupQuestion.objects.filter(category=category)
This view will simply serialize all your GroupQuestion models, which have related Categorie with the given id.
Finally, map this view in your urls and when you access this endpoint, you should get the expected result.
In your urls.py:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^groups/(?P<category_id>[0-9]+)/$', views.GroupQuestionList.as_view(), name='group_question_list')
]
Note: I am not sure that question_name_related is the valid related_name for the Question model in your GroupQuestion.