Django forms: how to use a query to show right options - django

I have the following model:
class Question(models.Model):
name = RichTextField (
verbose_name = 'Question'
)
QUESTION_TYPE = (
('Multi', 'Multiple Choice (one correct answer)'),
('Check', 'Multiple Answers')
)
question_type = models.CharField(
default = "Multi",
max_length = 7,
verbose_name = "Question Type",
choices = QUESTION_TYPE)
category = models.ForeignKey(
Category,
help_text = 'Category for this Question',
null = True
)
author = models.ForeignKey(
User,
)
quiz = models.ForeignKey (
Quiz,
)
def __unicode__(self):
return u'%s' % (self.name)
I am trying to use a Django form with it so I have this form:
class QuestionForm(forms.ModelForm):
class Meta:
model = Question
fields = ['id','question_type','category','name','author','quiz',]
widgets = {'id': forms.HiddenInput(),
'author': forms.HiddenInput(),
'quiz': forms.HiddenInput(),
'name': forms.TextInput(attrs={'class': 'form-control'}),
'question_type' :forms.Select(attrs={'class': 'form-control'}),
'category' :forms.Select(attrs={'class': 'form-control'}),
}
It works fine except the Category model is associated to a specific Quiz and I only want Categories for the Quiz to which the Question is linked.
class Category(models.Model):
name = models.CharField (
max_length = 30,
verbose_name = "Question Categories",
)
quiz = models.ForeignKey (
Quiz,
verbose_name = 'Quiz',
)
def __unicode__(self):
return u'%s' % (self.name)
I am trying to work out how to limit the Category models presented to the user through the form but I cannot work out how. Help much appreciated.

You could try something like below:
q_info = Question.objects.get(id=question_id)
Q = QuestionForm(instance=q_info)
Q.fields['category'].queryset = Category.objects.filter(condition)

Related

Unable to set form choices from view Django

I learned how to do this from Django Form ChoiceField set choices in View in Form initial
However it doesn't seem to work correctly, choices are not given
view:
form_class = AskMCQuestionForm()
mc_choices = []
if question.One:
mc_choices.append(tuple((1, question.One)))
if question.Two:
mc_choices.append(tuple((2, question.Two)))
if question.Three:
mc_choices.append(tuple((3, question.Three)))
if question.Four:
mc_choices.append(tuple((4, question.Four)))
if question.Five:
mc_choices.append(tuple((5, question.Five)))
mc_choices = tuple(mc_choices)
print(mc_choices)
form_class.fields['user_answer'].choices = mc_choices
form_class.fields['assignment'].initial = assignment
form_class.fields['exam'].initial = question.exam
form_class.fields['correct_answer'].initial = question.correct_answer
form_class.fields['text'].initial = question.text
print("About to render page for MC question")
return render(request, 'Exam/ask_question.html', {'question': question, 'form': form_class})
form:
class AskMCQuestionForm(forms.ModelForm):
class Meta:
model = MC_Question
fields = ('text', 'user_answer', 'assignment', 'correct_answer', 'exam',)
widgets = {
'text': forms.TextInput(attrs={'class': 'form-control', 'readonly': True}),
'user_answer': forms.Select(attrs={'class': 'form-control'}),
'assignment': forms.Select(attrs={'class': 'form-control'}),
'correct_answer': forms.HiddenInput(),
'exam': forms.HiddenInput(),
}
model:
class MC_Question(models.Model):
One = models.CharField(max_length=200)
Two = models.CharField(max_length=200)
Three = models.CharField(max_length=200, blank=True, null=True)
Four = models.CharField(max_length=200, blank=True, null=True)
Five = models.CharField(max_length=200, blank=True, null=True)
class Answers(models.IntegerChoices):
one = 1
two = 2
three = 3
four = 4
five = 5
text = models.CharField(max_length=200)
correct_answer = models.IntegerField(choices=Answers.choices, blank=True, null=True)
user_answer = models.CharField(max_length=200)
exam = models.ForeignKey(Test, on_delete=models.CASCADE)
assignment = models.ForeignKey(Assignment, on_delete=models.CASCADE, blank=True, null=True)
question is an object of MC_Question, the same as what the form is creating.
Apologies if I have left out an important detail, it's been several years since I posted on stackoverflow
By default the model form creates a TextInput to work with the user_answer field (is a models.CharField without choices), and the TextInput field does not know what to do with the choices argument.
You can try to assing the choices directly to the widget:
form_class.fields['user_answer'].widget.choices = mc_choices
Or add a custom field to your model form:
class AskMCQuestionForm(forms.ModelForm):
user_answer = forms.ChoiceField(...)

Django | Decimal Field is required even tho i set it to NULL=True

im trying to play a little bit around with django but i have run into problems...
I have a Decimal Field which is not required so i set it to "blank=True" and "null=True". But it still says its required :(
I also did all the migrations.
Here is my models.py
from django.db import models
weightUnit = {
('kg' , 'kilogram'),
('g', 'gram'),
('t', 'tons'),
('n', '-'),
}
class Product(models.Model):
pname = models.CharField(
max_length=50,
)
pdesc = models.TextField(
max_length=5000,
)
pprice = models.DecimalField(
max_digits=6,
decimal_places=2,
)
psn = models.CharField(
max_length = 30,
null=True,
blank=True,
)
pweightunit = models.CharField(
choices=weightUnit,
default='n',
null=True,
blank=True,
max_length=5,
)
pweight = models.DecimalField(
null=True,
blank = True,
max_digits=10000,
decimal_places=2,
)
plimage = models.ImageField(
blank=True,
null=True,
)
Here is my forms.py
from django import forms
from .models import weightUnit
class RawProductForm(forms.Form):
name = forms.CharField(label="Name")
desc = forms.CharField(label="Beschreibung")
price = forms.DecimalField(label="Stückpreis")
sn = forms.CharField(label="Seriennummer")
weightunit = forms.ChoiceField(choices=weightUnit, label="Gewichteinheit")
weight = forms.DecimalField(label="Gewicht")
image = forms.ImageField(label="Bild")
Here is my views.py
def product_add(request):
pf = RawProductForm()
if request.method == "POST":
pf = RawProductForm(request.POST)
if pf.is_valid():
print(pf.cleaned_data)
Product.objects.create(**pf.cleaned_data)
else:
print(pf.errors)
context = {
"productform" : pf,
}
return render(request, "product_add.html", context)
You are working with a simple Form, not a ModelForm [Django-doc], so that means that it will not inspect the model at all. It will simply render a form. A ModelForm will inspect the model and construct a form based on that that you can customize.
class RawProductForm(forms.ModelForm):
class Meta:
model = Product
labels = {
'name': 'Name',
'desc': 'Beschreibung',
'price': 'Stückpreis',
'sn': 'Seriennummer',
'weightunit': 'Gewichteinheit',
'weight': 'Gewicht',
'image': 'Bild',
}
A ModelForm also has a .save(…) method [Django-doc] which creates a model object based on the data in the form and saves it to the database.

How to solve is_hidden error of choicefield in Django model forms?

I am wokring on a form to save data where from and to days are choice fields in models.py. So I make my form Model in forms.py be choice fields. I've done this far.
models.py
class Create_Class(models.Model):
created_by = models.ForeignKey(
User, on_delete=models.SET_NULL, null=True, default=1)
class_name = models.CharField(max_length=150, null=True)
from_choice = (
('Mon', 'Monday'),
('Tue', 'Tuesday'),
('Wed', 'Wednesday'),
('Thu', 'Thursday'),
('Fri', 'Friday'),
('Sat', 'Saturday'),
('Sun', 'Sunday'),
)
from_days = models.CharField(max_length=10, choices=from_choice)
to_days = models.CharField(max_length=10, choices=from_choice)
from_time = models.TimeField()
to_time = models.TimeField()
created_on = models.DateTimeField(auto_now=True)
forms.py
class Create_Class_Model_Form(forms.ModelForm):
class Meta:
model = Create_Class
exclude = ['created_by', 'created_on']
fields = ['class_name', 'from_days', 'to_days', 'from_time', 'to_time']
from_choice = (
('Mon', 'Monday'),
('Tue', 'Tuesday'),
('Wed', 'Wednesday'),
('Thu', 'Thursday'),
('Fri', 'Friday'),
('Sat', 'Saturday'),
('Sun', 'Sunday'),
)
widgets = {
'class_name': forms.TextInput(attrs={'class': 'form-control'}),
'from_days': forms.ChoiceField(choices=from_choice, widget=forms.ChoiceWidget),
'to_days': forms.ChoiceField(choices=from_choice, widget=forms.Select()),
'from_time': forms.TimeInput(attrs={'class': 'form-control'}),
'to_time': forms.TimeInput(attrs={'class': 'form-control'}),
}
Views.py
def Class_create(request):
form = Create_Class_Model_Form(request.POST or None)
if form.is_valid():
obj = form.save(commit=False)
obj.created_by = request.user.username
obj.save()
print('successful')
messages.success(request, 'Saved Successfully')
c_names = Name_of_classes.objects.values('name')
templatename = 'genius/class_create.html'
context = {'form': form, 'c_names': c_names}
return render(request, templatename, context)
Error
ChoiceField' object has no attribute 'is_hidden'
The error is from forms.py
Your help to solve this error would be appreciated.
ChoiceField is not a widget. Just add it as a field and it should work.
class Create_Class_Model_Form(forms.ModelForm):
from_days = forms.ChoiceField(choices=<the choices>)
to_days = forms.ChoiceField(choices=<the choices>)
class Meta:
....

How can I customize url output?

I'm building a site using Django for equipment management. I am currently using this tutorial to help me get started.
I would like for the url displaying in the url bar in a browser to show something such as:
127.0.0.1:8000/catalog/model/model_number
But at the moment it just shows a single integer like 1, 2, 3, etc. when I click on each individual model.
Here is my urls.py:
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
path('books/', views.BookListView.as_view(), name='books'),
path('book/<int:pk>', views.BookDetailView.as_view(), name='book-detail'),
path('models/', views.ModelListView.as_view(), name='models'),
path('model/<int:pk>', views.ModelDetailView.as_view(), name='model-detail'),
]
I know that path('model/<int:pk>'... is the part that should be changed. I've tried path('model/model.model_number>'... but this outputs the below error:
Reverse for 'model-detail' with arguments '('2',)' not found. 1 pattern(s) tried: ['catalog\\/model\\/\\<model\\.model_number\\>$']
Here is my models.py:
from django.db import models
from django.urls import reverse #Used to generate URLs by reversing the URL patterns
import uuid
import datetime
# Create your models here.
class Genre(models.Model):
"""Model representing book genre"""
name = models.CharField(max_length = 200, help_text = 'Enter book genre')
def __str__(self):
"""String for represnting model object."""
return self.name
class Book(models.Model):
"""Model representing a book (but not a specific copy of a book)"""
title = models.CharField(max_length = 200)
author = models.ForeignKey('Author', on_delete = models.SET_NULL, null = True)
# Foreign Key used because book can only have one author, but authors can have multiple books
# Author as a string rather than object because it hasn't been declared yet in the file.
summary = models.TextField(max_length = 1000, help_text = 'Enter brief descp. of book') #TextField for longer descp.
isbn = models.CharField('ISBN', max_length = 13, help_text = '13 character ISBN number')
#ManyToManyField used because genre can contain many books. Books can cover many genres.
#Genre class has already been defined so we can specify the object above.
genre = models.ManyToManyField(Genre, help_text = 'Select genre for this book') #Genre capitalized, calling model up
def __str__(self):
"""String for representing the Model object."""
return self.title
def get_absolute_url(self):
"""returns the URL to access a detail record for this book."""
return reverse('book-detail', args=[str(self.id)])
class BookInstance(models.Model):
"""Model representing a specific copy of a book"""
id = models.UUIDField(primary_key = True, default = uuid.uuid4, help_text = 'Unique ID for this book across whole library')
book = models.ForeignKey('Book', on_delete=models.SET_NULL, null = True)
imprint = models.CharField(max_length = 200)
due_back = models.DateField(null = True, blank = True) #could do date scanned (date.now())
LOAN_STATUS = ( #could be ownership! Or make more of these for location
('m', 'Maintenance'), #being worked on, cleaned after field
('o', 'On loan'), #in the field
('a', 'Available'), #ready to go
('r', 'Reserved'), #in the field, could add one for 'being tested'
)
status = models.CharField(
max_length = 1,
choices = LOAN_STATUS,
blank = True,
default = 'm',
help_text = 'Book availability',
)
class Meta:
ordering = ['due_back']
def __str__(self):
"""String for representing the Model object."""
return f'{self.id} ({self.book.title})' #Will pop up on admin page as this
class Author(models.Model):
"""Model representing an author"""
first_name = models.CharField(max_length = 100)
last_name = models.CharField(max_length = 100)
date_of_birth = models.DateField(null = True, blank = True)
date_of_death = models.DateField('Died', null = True, blank = True)
class Meta:
ordering = ['last_name', 'first_name']
def get_absolute_url(self):
"""Returns the url to access a particular author instance."""
return reverse('author-detail', args=[str(self.id)])
def __str__(self):
"""String for representing the Model object."""
return f'{self.last_name}, {self.first_name}' #Will pop up on admin page as this
class Location(models.Model):
town_city_base = models.CharField('Town/City/Base', max_length = 25, help_text="If location is classified enter CLASSIFIED in CAPS")
state = models.CharField(max_length = 25)
country = models.CharField(max_length = 25)
class Meta:
ordering = ['town_city_base', 'state', 'country']
def get_absolute_url(self):
#Returns the url to access a particular location instance
return reverse('location-detail', args=[str(self.id)])
def __str__(self):
#String for representing the Model object
return f'{self.town_city_base}, {self.state}, {self.country}'
class Category(models.Model):
category = models.CharField('Equipment Type', max_length = 50, help_text = "Enter general category of equipment")
class Meta:
verbose_name_plural = "categories" #default would have shown as "Categorys" on admin page
def __str__(self):
return self.category
class Manufacturer(models.Model):
manufacturer = models.CharField(max_length = 50, help_text = "Original manufacturer or supplier")
def __str__(self):
return self.manufacturer
class Model(models.Model):
model_number = models.CharField('Model Number', max_length = 50)
manufacturer = models.ForeignKey('Manufacturer', on_delete = models.SET_NULL, null = True)
category = models.ForeignKey('Category', on_delete = models.SET_NULL, null = True)
description = models.TextField(max_length = 1000, help_text = "Enter brief description of product", null = True) #TextField for longer descriptions
def __str__(self):
return f'{self.model_number}..........{self.description}'
def get_absolute_url(self):
#Returns the url to access a particular location instance
return reverse('model-detail', args=[str(self.id)])
class Ownership(models.Model):
ownership = models.CharField('Ownership', max_length = 50)
class Meta:
verbose_name_plural = "Owners"
def __str__(self):
return self.ownership
def get_absolute_url(self):
return reverse('ownership-detail', args=[str(self.id)])
class ModelInstance(models.Model):
asset = models.CharField('Asset Number', max_length = 50, help_text = "Enter Weston or Government I.D. Tag Number")
model = models.ForeignKey('Model', on_delete = models.SET_NULL, null = True, help_text = "Leave blank if no model number exists")
serial = models.CharField('Serial Number', max_length = 50, null = True, blank = True, help_text = "Leave blank if no serial number exists")
location = models.ForeignKey('Location', on_delete = models.SET_NULL, null = True, help_text = "Enter current location or location after manufacturing")
ownership = models.ForeignKey('Ownership', on_delete = models.SET_NULL, null = True, help_text = "Enter current owner of equipment")
STATUS = (
('M', 'Maintenance'), #being worked on, not available
('D', 'Deployed'), #in the field
('A', 'Available'), #ready to go
('R', 'Reserved'), #Being used for classified/other project
('K', 'Broken'), #Broken
)
status = models.CharField(
max_length = 1,
choices = STATUS,
blank = True,
default = 'A',
help_text = 'Equipment Availability',
)
note = models.TextField('Notes', max_length = 1000, null = True, blank = True)
date_added = models.DateField("Date Added", default=datetime.date.today)
def __str__(self):
return f'Asset Number: {self.asset}..........Serial Number: {self.serial}..........Location: {self.location}..........Ownership: {self.ownership}'
You don't use the url to access the model's fields so
model.model_number>
is not going to help you.
If you want this url:
/catalog/model/model_number
You have to code it literally as it is:
path('catalog/model/model_number/<int:pk>/', views.ModelDetailView.as_view(), name='model-detail'),
]
It is good practice to add a backlash to the end.
Bear in mind that if the app name is catalog it is not necessary to add it to the path.
Now the pk of course is necessary to be passed to the view to retrieve the object you want, so you'll have this url in the browser:
127.0.0.1:8000/catalog/model/model_number/1/

Not all request payload making it to the serializer Django Rest Framework

A DRF newbie here, so please be patient with me :)
I'm trying to update an object with a Serializer and for some reason I can't get all the request payload to the Serializer, one of the fields is missing. I must say that this field is not part of the model that is linked with the Serializer, but looking at the docs, this seems not to be an issue...I can add whatever fields I want.
That said, this is my model:
class Product(AbstractProduct):
name = models.CharField(max_length=50, verbose_name=_(u'Name'))
school = models.ForeignKey('school.School')
level = models.ForeignKey(
'school.Level',
verbose_name=_(u'Level')
)
age = IntegerRangeField()
gender = models.CharField(choices=GENDER_CHOICES, max_length=1, null=True, blank=True, verbose_name=_(u'Gender'))
num_sessions = models.PositiveSmallIntegerField(verbose_name=_(u'Number of sessions'),
default=1,
help_text=_(u"Number of sessions that the product has."),
)
addons = models.ManyToManyField('self',
verbose_name=_(u'Administrators'),
through='AddonInService',
symmetrical=False,
related_name='addon_can_be_used_in'
)
class Meta(AbstractProduct.Meta):
verbose_name_plural = _(u'Products')
This is my ProductViewSet, nothing special as you can see...
class ProductAPIViewSet(NestedViewSetMixin, viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
#property
def pagination_class(self):
if 'offset' in self.request.query_params:
return CustomLimitOffsetPagination
else:
return CustomPageNumberPagination
Finally, here is my Serializer
class AddonSerializer(serializers.ModelSerializer):
price = serializers.SerializerMethodField()
class Meta:
model = Product
fields = (
'id',
'title',
'name',
'slug',
'description',
'price',
)
def get_price(self, obj):
Selector = get_class('partner.strategy', 'Selector')
selector = Selector()
strategy = selector.strategy()
stock_info = strategy.fetch_for_product(obj)
return stock_info.price.incl_tax
class ProductSerializer(serializers.ModelSerializer):
age = IntegerRangeField()
addons = AddonSerializer(many=True, read_only=True)
price = serializers.SerializerMethodField()
color = serializers.SerializerMethodField()
class Meta:
model = Product
fields = [
'id',
'structure',
'upc',
'title',
'slug',
'description',
'rating',
'date_created',
'date_updated',
'is_discountable',
'name',
'age',
'gender',
'num_sessions',
'parent',
'product_class',
'school',
'level',
'school_category',
'addons',
'color',
'price',
]
def get_price(self, obj):
Selector = get_class('partner.strategy', 'Selector')
selector = Selector()
strategy = selector.strategy()
stock_info = strategy.fetch_for_product(obj)
return stock_info.price.incl_tax
def validate(self, data):
school = data.get('school')
level = data.get('level')
view = self.context['view']
school_id = view.kwargs['parent_lookup_school']
school = School.objects.get(id=school_id)
if level and level.school != school:
raise serializers.ValidationError(
{'level': _(u'The level must be a level created by the school. ')
})
school_category = data.get('school_category')
if school_category is not None and school_category.school != school:
raise serializers.ValidationError({'school_category':
_(u'All the categories must be the categories created by the school.')}
)
return data
def get_color(self, obj):
try:
color = obj.school_category.color
except:
color = ''
return color
def create(self, validated_data):
product = Product.objects.create(**validated_data)
return product
def update(self, instance, validated_data):
for attr, value in validated_data.items():
if attr in 'price':
print('found price!!!')
else:
setattr(instance, attr, value)
instance.save()
return instance
Ok, now imagine in my front-end I create a request to modify an existing product, request payload is like this:
{
"school": 1,
"school_category": 1,
"name": "Surfing board",
"slug": "object-object",
"level": 3,
"num_sessions": 1,
"age": {
"upper": 65,
"lower": 18,
"bounds": "[)"
},
"addons": [{
"id": 6,
"title": "Photography",
"name": "Photography",
"slug": "",
"description": "Nice pics",
"price": 206.87
}],
"gender": "F",
"description": "a nice board",
"price": 2111
}
Thus, I'm expecting to get "2111" in the serializer update method, but nope, I'm getting nothing. Thus, I checked the validate method to see what was getting there:
(Pdb) l
85
86 def validate(self, data):
87 import pdb
88 pdb.set_trace()
89 school = data.get('school')
90 -> level = data.get('level')
91 view = self.context['view']
92 school_id = view.kwargs['parent_lookup_school']
93 school = School.objects.get(id=school_id)
94
95 if level and level.school != school:
(Pdb) print data
OrderedDict([(u'slug', u'object-object'), (u'description', u'a nice board'), (u'name', u'Surfing board'), (u'age', NumericRange(18, 65, u'[)')), (u'gender', 'F'), (u'num_sessions', 1), (u'school', <School: school_1>), (u'level', <Level: advanced>), (u'school_category', <Category: Surf <school_1>>)])
As you can see, there is no price neither addons being passed to the Serializer...
If I create an instance of the ProductSerializer, I can see the fields...
In [1]: from test.applications.catalogue.serializers import ProductSerializer
In [2]: ps = ProductSerializer()
In [3]: print repr(ps)
ProductSerializer():
id = IntegerField(label='ID', read_only=True)
structure = ChoiceField(choices=(('standalone', <django.utils.functional.__proxy__ object>), ('parent', <django.utils.functional.__proxy__ object>), ('child', <django.utils.functional.__proxy__ object>)), label='Product structure', required=False)
upc = CharField(allow_blank=True, allow_null=True, help_text='Universal Product Code (UPC) is an identifier for a product which is not specific to a particular supplier. Eg an ISBN for a book.', label='UPC', max_length=64, required=False, validators=[<UniqueValidator(queryset=Product.objects.all())>])
title = CharField(allow_blank=True, max_length=255, required=False)
slug = SlugField(max_length=255)
description = CharField(allow_blank=True, required=False, style={'base_template': 'textarea.html'})
rating = FloatField(read_only=True)
date_created = DateTimeField(read_only=True)
date_updated = DateTimeField(read_only=True)
is_discountable = BooleanField(help_text='This flag indicates if this product can be used in an offer or not', label='Is discountable?', required=False)
name = CharField(max_length=50)
age = IntegerRangeField()
gender = ChoiceField(allow_blank=True, allow_null=True, choices=(('M', <django.utils.functional.__proxy__ object>), ('F', <django.utils.functional.__proxy__ object>)), required=False)
num_sessions = IntegerField(help_text='Number of sessions that the product has.', label='Number of sessions', max_value=32767, min_value=0, required=False)
school = PrimaryKeyRelatedField(queryset=School.objects.all())
level = PrimaryKeyRelatedField(queryset=Level.objects.all())
school_category = PrimaryKeyRelatedField(allow_null=True, label='Category', queryset=Category.objects.all(), required=False)
addons = AddonSerializer(many=True, read_only=True):
id = IntegerField(label='ID', read_only=True)
title = CharField(allow_blank=True, max_length=255, required=False)
name = CharField(max_length=50)
slug = SlugField(max_length=255)
description = CharField(allow_blank=True, required=False, style={'base_template': 'textarea.html'})
price = SerializerMethodField()
color = SerializerMethodField()
price = SerializerMethodField()
Any idea why I'm not getting the price - addons in the Serializer? Thanks!
EDIT: Based on the documentation and on the above answer, I fixed this by changing the field to be an IntegerField
price = serializers.IntegerField()
And then moved added a method in the model itself
#property
def get_price(self):
Selector = get_class('partner.strategy', 'Selector')
selector = Selector()
strategy = selector.strategy()
stock_info = strategy.fetch_for_product(self)
return stock_info.price.incl_tax
This effectively works, and looks way cleaner...
This is because of the fact that you are using a SerializerMethodField for the price. According to the documentation:
This is a read-only field. It gets its value by calling a method on the serializer class it is attached to.
If you wish to pass an integer to your backend, you will have to use an IntegerField. In order to achieve your specialized functionality for retrieving the data, you can specify the source attribute:
price = serializers.IntegerField(source='get_price')
This get_price() method may have to be moved to the model in order for this to work right.