Restrict form field choices based on previous model choice in Django - python

I am currently attempting to create a DnD 5e Character creator using Django and SRD materials provided by WoTC. This is the first time I have ever used Django, and I am learning it as I go. I have come up against a bit of a challenge that has stone-walled me for a few days now. I have researched the issue, and after applying multiple techniques I thought may help, I've had limited luck. My question is this:
I have a number of models representing Heroes, Races, Subraces, Classes, Backgrounds and so forth. I wish to be able to restrict a users ability to choose a Subrace, based on their selection of a race beforehand.
So far I have this:
models.py
class Race(models.Model):
race_name = models.CharField(max_length=200)
race_size = models.CharField(
max_length=2, choices=SIZE_CHOICE, default='M')
race_speed = models.IntegerField(
default=30)
race_lang = models.CharField(max_length=200, null=True, blank=True)
race_str = models.IntegerField(default=0, null=True, blank=True)
race_dex = models.IntegerField(default=0, null=True, blank=True)
race_con = models.IntegerField(default=0, null=True, blank=True)
race_int = models.IntegerField(default=0, null=True, blank=True)
race_wis = models.IntegerField(default=0, null=True, blank=True)
race_cha = models.IntegerField(default=0, null=True, blank=True)
skill_spend = models.IntegerField(default=0, null=True, blank=True)
race_extra = models.TextField(max_length=2000, blank=True, null=True)
race_source = models.CharField(max_length=200, blank=True)
def __str__(self):
return self.race_name
class Meta:
verbose_name = 'Race'
verbose_name_plural = 'Races'
class Subrace(models.Model):
sub_name = models.CharField(max_length=200)
sub_size = models.CharField(
max_length=2, choices=SIZE_CHOICE, default='M', null=True)
sub_speed = models.IntegerField(
default=30, null=True)
sub_lang = models.CharField(max_length=200, null=True, blank=True)
sub_str = models.IntegerField(default=0, null=True, blank=True)
sub_dex = models.IntegerField(default=0, null=True, blank=True)
sub_con = models.IntegerField(default=0, null=True, blank=True)
sub_int = models.IntegerField(default=0, null=True, blank=True)
sub_wis = models.IntegerField(default=0, null=True, blank=True)
sub_cha = models.IntegerField(default=0, null=True, blank=True)
sub_extra = models.TextField(max_length=2000, null=True, blank=True)
sub_parent = models.ForeignKey(Race, on_delete=models.CASCADE, null=True)
def __str__(self):
return self.sub_name
class Meta:
verbose_name = 'Subrace'
verbose_name_plural = 'Subraces'
class Hero(models.Model):
def roll_stats():
d6 = die.Dice(6)
list_stats = d6.roll(4)
list_stats.sort()
add = sum(list_stats[1:4])
return add
hero_name = models.CharField(max_length=200)
author = models.ForeignKey(User, null=True)
hero_subrace = models.ForeignKey(
Subrace, on_delete=models.CASCADE, null=True, blank=True)
pub_date = models.DateTimeField('date published', blank=True, null=True)
hero_klass = models.ForeignKey(Klass, on_delete=models.CASCADE, null=True)
hero_race = models.ForeignKey(Race, on_delete=models.CASCADE)
background = models.ForeignKey(
Background, on_delete=models.CASCADE, null=True)
health = models.IntegerField(blank=True, null=True)
hero_exp = models.IntegerField(default=0, null=True)
hero_alignment = models.ForeignKey(Alignment, blank=True, null=True)
hero_str = models.IntegerField(default=roll_stats, null=True, blank=True)
hero_dex = models.IntegerField(default=roll_stats, null=True, blank=True)
hero_con = models.IntegerField(default=roll_stats, null=True, blank=True)
hero_int = models.IntegerField(default=roll_stats, null=True, blank=True)
hero_wis = models.IntegerField(default=roll_stats, null=True, blank=True)
hero_cha = models.IntegerField(default=roll_stats, null=True, blank=True)
def save(self, *args, **kwargs):
"Returns a hero's hp"
die_str = str(self.hero_klass.hit_dice)
die_nums = die_str.split("d")
die_val = int(die_nums[1])
die_roll = int(die_nums[0])
hp_die = die.Dice(die_val)
results = hp_die.roll(die_roll)
self.health = sum(results)
super(Hero, self).save(*args, **kwargs)
def __str__(self):
return self.hero_name
def get_absolute_url(self):
return reverse('hero.views.detail', args=[str(self.id)])
class Meta:
verbose_name = 'Hero'
verbose_name_plural = 'Heroes'
views.py
def new_hero(request):
user = request.user
if request.method == "POST":
form = HeroForm(request.POST)
if form.is_valid():
hero = form.save(commit=False)
hero.author = request.user
hero.save()
return redirect('detail', hero.pk)
else:
form = HeroForm()
return render(request, 'new_hero.html', {'form': form, 'user': user})
forms.py
class HeroForm(forms.ModelForm):
class Meta:
model = Hero
fields = ['hero_name', 'hero_race', 'hero_subrace',
'hero_klass', 'hero_exp', 'health', 'background',
'hero_str', 'hero_dex', 'hero_con', 'hero_int',
'hero_wis', 'hero_cha', 'hero_alignment']
def __init__(self, *args, **kwargs):
super(HeroForm, self).__init__(*args, **kwargs)
for fieldname in ['hero_str', 'hero_dex', 'hero_con', 'hero_int', 'hero_wis', 'hero_cha']:
self.fields[fieldname].disabled = True
race = Race.objects.all()
for name in race:
self.fields['hero_subrace'].queryset = Subrace.objects.filter(sub_parent=name)
I have trialled a few different techniques, but this is where I am now. This:
for name in race:
self.fields['hero_subrace'].queryset = Subrace.objects.filter(sub_parent=name)
is my most recent addition to my app. At the hero creation screen I am hit with a blank box of choices, as opposed to the full unrestricted list without the loop or queryset.
Basically I'm hoping that someone has some advice for me on a method I may be overlooking, or something that I've missed, or simply not found yet. Also please feel free to critique the rest of the code, like I said this is my first Django App :). Also my first Stack Overflow question, so thanks :)

For anyone that is wondering, I used django-smart-selects to solve my problem.
base.html
<script type="text/javascript" src="{% static 'smart-selects/admin/js/chainedfk.js' %}"></script>
<script type="text/javascript" src="{% static 'smart-selects/admin/js/bindfields.js' %}"></script>
I added the above html to my {% load staticfiles %} call.
and changed models.py:
models.py
from smart_selects.db_fields import ChainedForeignKey
class Hero(models.Model):
....
race = models.ForeignKey(Race, on_delete=models.CASCADE)
subrace = ChainedForeignKey(Subrace,
chained_field="race",
chained_model_field="race",
show_all=False,
auto_choose=True,
blank=True,
null=True)
Now I have a subrace field that is dynamically update when the user chooses a Race.

Related

How can I solve this indentation problem in my Django project

I'm a beginner in Django.
I was trying to add a method inside the OrderItem class. But the visual studio code is showing an indentation error.
I'm not sure what is wrong here.
Anyone can help me, please?
Here is the code:
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class Customer(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True, blank=True)
name = models.CharField(max_length=200, null=True)
email = models.CharField(max_length=200, null=True)
def __str__(self):
return self.name
class Product(models.Model):
name = models.CharField(max_length=200, null=True)
price = models.FloatField()
digital = models.BooleanField(default=False, null=True, blank=True)
#image needs to be added
image = models.ImageField(null=True, blank=True)
def __str__(self):
return self.name
#property
def imageURL(self):
try:
url = self.image.url
except:
url = ""
return URL
class Order(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, null=True, blank=True)
date_ordered = models.DateTimeField(auto_now_add=True)
complete = models.BooleanField(default=False, null=True, blank=True)
transaction_id = models.CharField(max_length=200, null=True)
def __str__(self):
return str(self.id)
class OrderItem(models.Model):
product = models.ForeignKey(Product, on_delete=models.SET_NULL, null=True)
order = models.ForeignKey(Order, on_delete=models.SET_NULL, null=True)
quantity = models.IntegerField(default=1, null=True, blank=True)
date_added = models.DateTimeField(auto_now_add=True)
#property
def get_total(self):
return self.product.price * self.quantity
The indentation error is showing on the #property and get_total()
class OrderItem(models.Model):
product = models.ForeignKey(Product, on_delete=models.SET_NULL, null=True)
order = models.ForeignKey(Order, on_delete=models.SET_NULL, null=True)
quantity = models.IntegerField(default=0, null=True, blank=True)
date_added = models.DateTimeField(auto_now_add=True)
#property
def get_total(self):
total = self.product.price * self.quantity
return total
Pelase see the attached screenshot.
The #property decorator should be indented at the same level of the method and fields, so:
class OrderItem(models.Model):
product = models.ForeignKey(Product, on_delete=models.SET_NULL, null=True)
order = models.ForeignKey(Order, on_delete=models.SET_NULL, null=True)
quantity = models.IntegerField(default=1, blank=True)
date_added = models.DateTimeField(auto_now_add=True)
#property
def get_total(self):
return self.product.price * self.quantity
Likely you want the quantity to be by default 1. It is also not clear to me why that is a nullable field.
As a general rule-of-thumb please do not mix spaces and tabs. It might be better to always use spaces.

How to filter Many to Many field in django admin page using a foreign key value?

I am trying to filter my many to many variation fields with respect to the product. means, I only want the variations related to the current product to show in the admin page. now its showing all the variations available for every product.
I added formfield_for_manytomany() function to my admin.py but how can I get the current product(id) in the cart or order to filter the variations?
most of the questions in stack overflow Is based on the current user, which is easy to get? but how should I get the specific product(id) that is opened in the admin panel.
admin.py
from django.contrib import admin
from .models import *
from products.models import Variation
class CartAdmin(admin.ModelAdmin):
list_display = ('cart_id', 'date_created')
class CartItemAdmin(admin.ModelAdmin):
list_display = ('user','cart', 'product', 'quantity','is_active')
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == "variation":
product = Products.objects.get(id='??') # how I get the current product in the cart or order
kwargs["queryset"] = Variation.objects.filter(product=product.id)
return super().formfield_for_manytomany(db_field, request, **kwargs)
admin.site.register(Cart, CartAdmin)
admin.site.register(CartItem, CartItemAdmin)
CartItem Model
class CartItem(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, null=True)
cart = models.ForeignKey(Cart, on_delete=models.CASCADE, null=True)
product = models.ForeignKey(Products, on_delete=models.CASCADE)
variation = models.ManyToManyField(Variation, blank=True)
quantity = models.IntegerField()
is_active = models.BooleanField(default=True)
created_date = models.DateTimeField(auto_now_add=True)
def item_total(self):
return self.product.price * self.quantity
def __str__(self):
return self.product.name
Product and Variation Model
class Products(models.Model):
name = models.CharField(max_length=50, unique=True)
slug = AutoSlugField(populate_from='name', max_length=100, unique=True)
isbn = models.CharField(max_length=20, unique=True, blank=True, null=True)
sub_category = models.ForeignKey(SubCategory, on_delete=models.CASCADE)
language = models.ForeignKey(Language, on_delete=models.SET_NULL, null=True)
author = models.CharField(max_length=100)
Publisher = models.CharField(max_length=100, blank=True, default=None)
release_date = models.DateField(blank=True, null=True, default=None)
price = models.IntegerField(default=None)
stock = models.IntegerField(default=None)
is_available = models.BooleanField(default=True)
cover_image = models.ImageField(upload_to='images/products')
image1 = models.ImageField(upload_to='images/products', blank=True, default=None, null=True)
image2 = models.ImageField(upload_to='images/products', blank=True, default=None, null=True)
image3 = models.ImageField(upload_to='images/products', blank=True, default=None, null=True)
description = models.TextField(max_length=2000, blank=True, default=None)
create_date = models.DateTimeField(auto_now_add=True)
modified_date = models.DateTimeField(auto_now=True)
number_of_pages = models.IntegerField(blank=True, null=True)
weight = models.IntegerField(blank=True, null=True)
width = models.IntegerField(blank=True, null=True)
height = models.IntegerField(blank=True, null=True)
spine_width = models.IntegerField(blank=True, null=True)
class Meta:
verbose_name = 'Product'
verbose_name_plural = 'Products'
def get_url(self):
return reverse('product-view', args=[self.slug])
def __str__(self):
return self.name
class Variation(models.Model):
product = models.ForeignKey(Products, on_delete=models.CASCADE)
variation_category = models.CharField(max_length=100, choices=variation_category_choice)
variation_value = models.CharField(max_length=100, choices=variation_value_choice)
is_available = models.BooleanField(default=True)
date_added = models.DateTimeField(auto_now_add=True)
objects = VariationManager()
def __str__(self):
return self.variation_value

Create list of Approvers Django

I am trying to create an application where you can create project-based expenses, and each project should have a list of approvers who will approve the expense once uploaded to the project, how do I replicate this in Django.
What I tried to do.
I got the approvers into the form as a ModelMultipleChoiceField
self.fields['approvers'] = forms.ModelMultipleChoiceField(queryset=Profile.objects.all())
and saved it as a list into the database
def getApprovers(approvers):
id = []
for profile in approvers:
id.append(profile.id)
return id
in project create function
project.approvers = getApprovers(form.cleaned_data['approvers'])
I created a templatetag to display the list of approvers as the Profile Queryset objects. I tried to iterate over the saved list of approvers, but I keep getting errors
def approvers_list(approvers):
for profile in list(approvers):
profiles = [Profile.objects.filter(pk = i) for i in
list(approvers)]
return profile
I got this error
Field 'id' expected a number but got '['.
This is the result I got after changing the approvers_list method
def approvers_list(approvers):
p = []
for profile in list(approvers):
p.append(profile)
return p
This is the result
['[', '1', ',', ' ', '2', ',', ' ', '3', ']']
How can I turn this into a list of ids, so I can use it to get individual profiles of the approvers?
How can I get this to work best?
Here are the models
class Project(MiscClass):
owner = models.ForeignKey(Profile, on_delete=models.CASCADE, null=True, blank=True)
organisation = models.ForeignKey(Organisation, on_delete=models.SET_NULL, null=True, blank=True)
title = models.CharField(max_length=200, null=True, blank=True, unique=True)
slug = models.SlugField(max_length=500, null=True, blank=True, unique=True)
category = models.ForeignKey('Category', on_delete=models.CASCADE, null=True, blank=True)
description = models.TextField(null=True, blank=True)
approvers = models.CharField(max_length=100, null=True, blank=True, validators=[int_list_validator])
class Meta:
ordering = ['-created']
def __str__(self):
return str(self.title)
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super(Project, self).save(*args, **kwargs)
class Expense(MiscClass):
PROCESSING = 1
APPROVED = 2
DECLINED = 3
APPROVAL_STATUS = (
(PROCESSING, 'Processing'),
(APPROVED, 'Approved'),
(DECLINED, 'Declined')
)
owner = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True)
title = models.CharField(max_length=300, null=True)
slug = models.SlugField(max_length=300 , null=True, unique=True)
amount = models.CharField(max_length=200, null=True, blank=True)
status = models.IntegerField(choices=APPROVAL_STATUS, default=PROCESSING)
description = models.TextField(blank=True, null=True)
refrence_number = models.CharField(max_length=300, unique=True, null=True)
category = models.ForeignKey('Category', on_delete=models.SET_NULL, null=True, blank=True)
date_due = models.DateTimeField(auto_now_add=False, null=True, blank=True)
merchant = models.CharField(max_length=200, null=True, blank=True)
expense_date = models.DateTimeField(auto_now_add=False, null=True, blank=True)
def __str__(self):
return str(self.title)
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
return super(Expense, self).save(*args, **kwargs)

Django many-to-many fields

i want to add in only one user from Available classic to Chosen classic but not from admin page from view when user click on button
I tried this
pro = Profile(user=request.user)
pro.classic.add(name='Frank Sinatra-i love you baby')
but i get tthis error
Before adding any objects to a many-to-many relation, the both side of many-to-many objects must obtain their id, so they should be saved first.
Another problem in this code is you need to construct a Classic object before you add it to chosen ones. The add method receives objects, not parameters of model constructor.
May following code example helps you:
pro, _ = Profile.objects.get_or_create(user=request.user)
your_classic = Classic.objects.create(name='Frank Sinatra-i love you baby')
pro.classic.add(your_classic)
give me this error
mu model code
from django.db import models
# Create your models here.
from django.utils import timezone
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
class RequestSong(models.Model):
music = models.CharField(max_length=150, blank=False, null=False)
stampDate = models.DateTimeField(default=timezone.now, blank=False, null=False)
def __str__(self):
return self.music
class HelpUser(models.Model):
email = models.EmailField(blank=False, null=False)
helpMessege = models.TextField(blank=False, null=False)
stampDate = models.DateTimeField(default=timezone.now)
def __str__(self):
return str(self.email)
class Language(models.Model):
language = models.CharField(
max_length=2,
choices=[
('AR', 'Arabic'),
('EN', 'English'),
],
default='AR'
)
def __str__(self):
return self.language
class ChillOut(models.Model):
name = models.CharField(max_length=50, blank=False, null=False)
music = models.FileField(upload_to='', max_length=100, blank=True, null=True)
lang = models.ForeignKey(Language, on_delete=models.CASCADE)
def __str__(self):
return self.name
class Sad(models.Model):
name = models.CharField(max_length=50, blank=False, null=False)
music = models.FileField(upload_to='', max_length=100, blank=True, null=True)
lang = models.ForeignKey(Language, on_delete=models.CASCADE)
def __str__(self):
return self.name
class Happy(models.Model):
name = models.CharField(max_length=50, blank=False, null=False)
music = models.FileField(upload_to='', max_length=100, blank=True, null=True)
lang = models.ForeignKey(Language, on_delete=models.CASCADE)
def __str__(self):
return self.name
class Romantic(models.Model):
name = models.CharField(max_length=50, blank=False, null=False)
music = models.FileField(upload_to='', max_length=100, blank=True, null=True)
lang = models.ForeignKey(Language, on_delete=models.CASCADE)
def __str__(self):
return self.name
class WorkOut(models.Model):
name = models.CharField(max_length=50, blank=False, null=False)
music = models.FileField(upload_to='', max_length=100, blank=True, null=True)
lang = models.ForeignKey(Language, on_delete=models.CASCADE)
def __str__(self):
return self.name
class Classic(models.Model):
name = models.CharField(max_length=50, blank=False, null=False)
music = models.FileField(upload_to='', max_length=100, blank=True, null=True)
lang = models.ForeignKey(Language, on_delete=models.CASCADE)
def __str__(self):
return self.name
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
classic = models.ManyToManyField(Classic, blank=True, null=True)
workOut = models.ManyToManyField(WorkOut, blank=True, null=True)
chillOut = models.ManyToManyField(ChillOut, blank=True, null=True)
romantic = models.ManyToManyField(Romantic, blank=True, null=True)
happy = models.ManyToManyField(Happy, blank=True, null=True)
sad = models.ManyToManyField(Sad, blank=True, null=True)
def __str__(self):
return str(self.user)
# class Favorite(models.Model):
# name = models.CharField(max_length=50, blank=False, null=False)
# fav = models.ForeignKey(ChillOut, on_delete=models.CASCADE)
# pro = models.ForeignKey(Profile, on_delete=models.CASCADE)
# def __str__(self):
# return self.name
def update_user_profile(sender, **kwargs):
if kwargs['created']:
user = Profile.objects.create(user=kwargs['instance'])
post_save.connect(update_user_profile,sender=User)

Last element i a django foreignkey

I'm struggling getting the right query for my project. How can i get the last lesson in a specific module . I intended to show a message to a user when he completes the last lesson of a module i.e "Congrats use you have completed module blah blah blah" this message needs only to be flashed once the last lesson of a module has been completed.
class Module(models.Model):
DRAFT = 1
SUSPENDED = 2
PUBLISHED = 3
MODULE_STATUSES = (
(DRAFT, _("Draft")),
(SUSPENDED, _("Suspended")),
(PUBLISHED, _("Published"))
)
course = models.ForeignKey(Course)
name = models.CharField(verbose_name=_("Module Name"), max_length=200, help_text=_('The module title'))
description = models.TextField(verbose_name="Module Description", help_text=_("Module description"))
prerequisite = models.ForeignKey('self', verbose_name=_("Prerequisite Module"), null=True, blank=True, default=None)
slug = models.SlugField(editable=False, help_text=_('Unique identifier'))
status = models.IntegerField(verbose_name="Module Status", choices=MODULE_STATUSES, default=PUBLISHED)
position = PositionField(collection='course', default=0)
end_of_module = models.TextField(verbose_name=_("Module Summary"), default=None, null=True, help_text=_("Wrap and summarize this module"))
last_module = models.ForeignKey('self', verbose_name=_("Previous Module"), null=True, blank=True, default=None, related_name="previous_module")
next_module = models.ForeignKey('self', verbose_name=_("Next Module"), null=True, blank=True, default=None, related_name="the_next_module")
class Meta:
verbose_name_plural = "Modules"
def __unicode__(self):
return self.name
def get_absolute_url(self):
return reverse('module_detail', args=[self.slug])
class Lesson(models.Model):
module = models.ForeignKey(Module)
name = models.CharField(verbose_name=_("Lesson Title"), max_length=200, help_text=_('The lesson title'))
prerequisite = models.ForeignKey('self', verbose_name=_("Prerequisite Lesson"), null=True, blank=True, default=None)
content = models.TextField()
instructions_before = models.TextField(verbose_name=_("Instructions before exercise"), null=True, blank=True, default=None)
instructions_after = models.TextField(verbose_name=_("Instructions after exercise"), null=True, blank=True, default=None)
quizzes = models.ManyToManyField(Quiz, verbose_name=_("Quiz"), blank=True, related_name='lessons')
slug = models.SlugField(editable=False, help_text=_('Unique identifier'))
exercises = models.ManyToManyField(Exercise, verbose_name=_("Exercise"), blank=True, related_name='lesson_exercises')
position = PositionField(collection='module', default=0)
class Meta:
verbose_name = _("Lesson")
verbose_name_plural = _("Lessons")
def __unicode__(self):
return self.name
You should filter all the Lessons by Lesson.module. Then, order by Lesson.position (reverse).

Categories

Resources