Django: set model choice field options to queryset values and other value - django

In the below Django models
class Post(models.Model):
author = models.ForeignKey(CustomUser, on_delete=models.CASCADE,)
title = models.CharField(max_length=200, null=True)
text = models.TextField()
post_url = models.URLField(max_length = 200, blank=True)
post_type = models.IntegerField()
slug = models.SlugField(unique=True, blank=True)
class Tiers(models.Model):
user = models.ForeignKey(CustomUser, default=None, null=True, on_delete=models.CASCADE,)
tier_name = models.CharField(max_length=128, blank=True)
tier_value = models.IntegerField()
slug = models.SlugField(unique=True, blank=True)
I want to use post model for a form like below
class PostForm(forms.ModelForm):
class Meta:
model = models.Post
fields = ('title', 'text', 'post_url', 'post_type')
But for post_type field I want to display as dropdown with options from Tiers models tier_value. For example if user1 has 3 entries in Tiers model with tier_values as 10, 20 and 30. I want to display 4 options 0, 10, 20 , and 30. Can someone help me how to achieve this?

In theory, you can override the FormField django generates. In this case, you could use a Select widget for the post_type field.
However, I think with your modeling can be improved/normalized, and if that is done, your issue will resolve. Consider these models:
class Tier(models.Model):
name = models.CharField(max_length=128, blank=True)
value = models.IntegerField()
slug = models.SlugField(unique=True, blank=True)
class CustomUser(...):
tiers = models.ManyToManyField(Tier, related_name='users')
...
class Post(models.Model):
author = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
tier = models.ForeignKeyField(Tier)
title = models.CharField(max_length=200, null=True)
text = models.TextField()
url = models.URLField(max_length = 200, blank=True)
slug = models.SlugField(unique=True, blank=True)
This way, you different users can have the same tier (which is probably what you want?) without duplication of the name and level values.
Now, when you create ModelForm with Post, it will automatically give you select field for all existing tiers. However, you just want to be able to select the tiers the user is in, so you would set a custom queryset for that field:
class PostForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['tier'].queryset = Tier.objects.filter(users__in=[user])
class Meta:
model = models.Post
fields = ('title', 'text', 'url', 'tier')
Edit: I just now saw that you want to allow all tiers that the user is in or that have less value. You can do this the same way, you just have to adapt the queryset:
max_tier_value = user.tiers.aggregate(Max('value')).value__max
self.fields['tier'].queryset = Tier.objects.filter(value__lte=max_tier_value)
However, you probably want to do either of these two, but not both:
Each user is assigned with a tier level and can create posts with any lower tier level.
Each user is assigned with multiple tier levels and can only create posts with these.
So, if you go with this queryset, you should remodel so that CustomUser.tier is a models.ForeignKey(Tier, related_name='users')

Related

Django - ForeignKey Filter Choices

I'd like to filter the choices that a user can choose in my ForeignKey Field.
I basically have a ForeignKey for the subject of the Test and the actual topic of the Test. These topics come from a different model and are linked to a subject. Now I'd like to filter the choices to only include the topics that are linked to the currently selected subject. Is that possible and if so, how?
models.py
class Test(models.Model):
student = models.ForeignKey(Person, on_delete=models.CASCADE, blank=True, null=True)
subject = models.ForeignKey(Subject, on_delete=models.CASCADE, blank=True, null=True)
thema = models.ForeignKey(Thema, on_delete=models.CASCADE, blank=True, null=True)
school_class = models.ForeignKey(SchoolClass, on_delete=models.CASCADE, blank=True, null=True)
grade = models.FloatField(validators=[MinValueValidator(0), MaxValueValidator(6)], blank=True, null=True)
date = models.DateField(default=datetime.date.today)
def save(self, *args, **kwargs):
if not self.school_class and self.student:
self.school_class = self.student.klasse
return super().save(*args, **kwargs)
class Thema(models.Model):
subject = models.ForeignKey(Subject, on_delete=models.CASCADE, blank=True, null=True)
thema = models.CharField(max_length=50)
class Subject(models.Model):
teacher = models.ForeignKey(Person, on_delete=models.CASCADE, blank=True, null=True)
name = models.CharField(max_length=20)
The Problem if I use this:
# thema model #staticmethod
def return_thema(subject):
themen = Thema.objects.filter(subject=subject)
return {'thema': themen}
#test model
thema = models.ForeignKey(Thema, on_delete=models.CASCADE, blank=True, null=True,limit_choices_to=Thema.return_thema(subject))
Is that I get the Error:
django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet.
Meaning I can't get the objects of the Thema Model while the models are loading
EDIT (for Swift):
That seemed to resolve the error when trying to makemigrations, but I now get this error, when visiting the admin portal to create a new Test:
File "/Users/di/Code/Schule/GymnasiumApp/venv/lib/python3.10/site-packages/django/db/models/sql/query.py", line 1404, in build_filter
arg, value = filter_expr
ValueError: too many values to unpack (expected 2)
I think what you are looking for ideally would be ForeignKey.limit_choices_to
Please see the docs:
https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.ForeignKey.limit_choices_to
You can limit the choices available at a model level, which is enforced throughout the django app, including forms automatically.
Edit because OP provided more information
Ok so I believe if you declare the thema field on the test model like so, it will solve the issue, and I will explain why after:
class Test(models.Model):
student = models.ForeignKey(Person, on_delete=models.CASCADE, blank=True, null=True)
subject = models.ForeignKey(Subject, on_delete=models.CASCADE, blank=True, null=True)
thema = models.ForeignKey(Thema, on_delete=models.CASCADE, blank=True, null=True, limit_choices_to=Q('thema_set__subject_set'))
school_class = models.ForeignKey(SchoolClass, on_delete=models.CASCADE, blank=True, null=True)
grade = models.FloatField(validators=[MinValueValidator(0), MaxValueValidator(6)], blank=True, null=True)
date = models.DateField(default=datetime.date.today)
def save(self, *args, **kwargs):
if not self.school_class and self.student:
self.school_class = self.student.klasse
return super().save(*args, **kwargs)
We are essentially telling Django to evaluate the relationship between the limited choices "lazily" I.e. when the form is loaded dynamically. Django forms will look at the limit_choices_to field argument and apply it to the available choices.
I'm not 100% about the relationship of your models so the Q(...) I added, might actually need to be Q('subject_set')
If you use django forms you can use the model choice field.
In your view you can set your queryset of this choicefield. Zo you can filter it.
fields['your model field'].queryset = yourmodel.objects.filter(your filter parameters)
I think there is also problem in save method also. Aren't you need to write the name of the model inside like
return super(<modelName>).save(*args, **kwargs)

How to add Category while adding product in Django form? Just like the Django admin form

So I am trying to build an inventory system.
I have 2 models, Categories and Product connected through the ManyToMany field.
I want to add a category while I am adding the product just like it happens in the Django admin form.
How can I do that?
My model.py File
class Categories(models.Model):
name = models.CharField(max_length=30)
organisation = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
def __str__(self):
return f"{self.name}"
class Product(models.Model):
category = models.ManyToManyField(Categories)
brand = models.CharField(max_length=20)
model = models.CharField(max_length=20)
hac = models.CharField(max_length=20, null=True, blank=True)
rate = models.DecimalField(max_digits=6, decimal_places=2)
stock = models.IntegerField(default=0)
# organisation = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
date_added = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.brand} {self.model} "
My form.py file
class ProductModelForm(forms.ModelForm):
class Meta:
model = Product
fields = '__all__'
**My code output **
See the below screenshots to understand what I want to do. I basically want that plus button option to add a category from the product form itself.
You should show snippets of codes that we need to provide an answer.

how to have database relationship with the active data. in django

i have some active and non active data's in EVENT models and active data has the VISITORS form to fill ..so far i have tried OnetoOne relationship but it didn't succeed ..i am getting both active and non active field in VISITORs model..thank you for your time.
here is models.py
class Event(models.Model):
event_id = models.AutoField
Event_Name = models.CharField(max_length=50)
description = RichTextField()
date_And_time = models.DateTimeField()
location=models.CharField(max_length=50)
slugs = models.SlugField(max_length= 200,default="")
picture = models.ImageField(upload_to='wildlife/picture', default="")
active = models.BooleanField(default=False)
class Meta:
ordering = ["date_And_time"]
def __str__(self):
return self.Event_Name
class Eventvisitor(models.Model):
event = models.OneToOneField(Event, on_delete=models.CASCADE, related_name="eventvistor",default="")
name = models.CharField(max_length=50)
email = models.CharField(max_length=70, default="")
phone = models.CharField(max_length=20,default="")
date = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-date']
def __str__(self):
return self.email
You can limit the choices with limit_choices_to=… [Django-doc]. But likely what you want is a ForeignKey, since otherwise, each Event can have at most one related EventVisitor (a OneToOneField is basically a ForeignKey with a unique=True constraint).
class Eventvisitor(models.Model):
event = models.ForeignKey(
Event,
limit_choices_to={'active': True},
on_delete=models.CASCADE,
related_name='eventvistors'
)
name = models.CharField(max_length=50)
email = models.CharField(max_length=70, default="")
phone = models.CharField(max_length=20,default="")
date = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-date']
def __str__(self):
return self.email
Note that while one can only select Events with active=True, if you later set the .active field to False, items will still link to it, since the relation is not enforced at database level.

django charge users based on how many counties they want to access

I'm building a web app,
basically I currently have 3 models ,
1- State: which represents all US states
2- County: which represents all counties with foreign key of state
3- Home: which represents all homes with foreign key of County
the app will show homes,
but users needs to subscribe for certain counties (the counties prices can vary)
the goal is : when users subscribe to certain counties they can see the related "Homes" to these counties
I'm not sure how should I represent these relations between users, subscriptions and how to connect it to County model I have.
and how to make a view for the user to add new counties.
Thank you.
Update (My models):
class State(models.Model):
state_name = models.CharField(max_length=50)
def __str__(self):
return self.state_name
class County(models.Model):
county_name = models.CharField(max_length=50)
state = models.ForeignKey(State, on_delete=models.CASCADE)
def __str__(self):
return self.county_name
class Meta:
unique_together = ("county_name", "state")
verbose_name_plural = 'Counties'
class Home(models.Model):
owner_name = models.CharField(max_length=100, null=True, blank=True)
street_address = models.CharField(max_length=100, null=True, blank=True)
city = models.CharField(max_length=50, null=True, blank=True)
county = models.ForeignKey(County, on_delete=models.CASCADE)
postal_code = models.CharField(max_length=50, null=True, blank=True)
price = models.CharField(max_length=50, null=True, blank=True)
sqft = models.CharField(max_length=50, null=True, blank=True)
home_type = models.CharField(max_length=100, null=True, blank=True)
geom = models.PointField()
added = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
return '{}, {}, {}'.format(self.street_address, self.city, self.county.state.state_name)
class Meta:
verbose_name = 'Home'
verbose_name_plural = 'Homes'
#property
def state_county(self):
return f'{self.county.county_name}_{self.state}'
#property
def state(self):
return self.county.state.state_name
Here is a basic idea, you should evaluate from this point.
class State(models.Model)
name = models.CharField(max_length=100)
class County(models.Model)
state = models.ForeignKey(State)
name = models.CharField(max_length=100)
class Home(models.Model)
county= models.ForeignKey(County)
name = models.CharField(max_length=100)
class Subscription(models.Model)
county = models.ForeignKey(County)
user = models.ForeignKey(User)
Basically, you can then charge your user per County (observe that one can have more than one County subscription)
Another aproach would be to use a hierarchy to have State>County>Home, on a MPTT, but maybe its not what you want.
One way would be to add ManyToMany County relationship field in the Subscriptions model and then you would query subscribed county and filter Home.
Something in the sense of:
class County(models.Model):
county = models.CharField(max_length=255)
class Home(models.Model):
county = models.ForeignKey(County, on_delete=models.PROTECT)
class Subscription(models.Model):
user = models.ForeingKey(User, on_delete=models.PROTECT)
county = models.ManyToMany(County)
Then you'd query subscriptions and filter based on that.
subscriptions = Subscription.objects.filter(user=request.user).values_list('county', flat=True)
homes = Home.objects.filter(county_id__in=subscriptions)
You could further improved that with models Manager on Subscription to avoid filtering user every time with something like:
class SubscriptionManager(models.Manager):
def user_subscriptions(self, user):
return super().get_queryset().filter(user=user)
class Subscription(models.Model):
user = models.ForeingKey(User, on_delete=models.PROTECT)
county = models.ManyToMany(County)
objects = SubscriptionManager()
and then filter either with:
subscriptions = Subscription.objects.filter(user=request.user).values_list('county', flat=True)
or
subscriptions = Subscription.objects.user_subscriptions(request.user).values_list('county', flat=True)

Create relationship between two models using django class based views

I have two models Company and Campaign. I need to create a relationship between them. I think my models are fine.
companies/model.py
class Company(models.Model):
class Meta:
verbose_name_plural = "companies"
user = models.ForeignKey(settings.AUTH_USER_MODEL)
title = models.CharField(blank=False, max_length=128, default='')
slug = models.SlugField(blank=True, unique=True)
archived = models.BooleanField(default=False)
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
campaigns/models.py
class Campaign(models.Model):
class Meta:
verbose_name_plural = "campaigns"
company = models.ForeignKey('companies.Company', on_delete=models.CASCADE,)
title = models.CharField(blank=False, max_length=128, default='')
slug = models.UUIDField(default=uuid.uuid4, blank=True, editable=False)
def __str__(self):
return str(self.title)
campaigns/forms.py
class CampaignForm(forms.ModelForm):
class Meta:
model = Campaign
fields = ['title','description','archived']
campaigns/views.py
class CampaignCreateView(SubmitBtnMixin, CreateView):
model = Campaign
company = None
form_class = CampaignForm
submit_btn = "Add Campaign"
template_name = "form.html"
campaigns/urls.py
url(r'^campaign/create/$', CampaignCreateView.as_view(), name='campaign-create'),
My question is, when creating a new campaign, where and how do I pick up the Company pk to populate the Campaign model? What is the most secure and best practice for doing this?
I found a solution but would like input on best practices still.
I added this to my CampaignCreateView
def form_valid(self, form):
company = get_object_or_404(Company, id=self.kwargs.get('pk'), user_id=self.request.user.id)
form.instance.company_id = company.id
return super(CampaignCreateView, self).form_valid(form)
and I changed my url to:
url(r'^campaign/(?P<pk>\d+)/create/$', CampaignCreateView.as_view()...
Not sure that I like the pk in the URL since it can be jacked. This is why I am filtering on the userid at the company model to make sure that the data is coming from the owner.
I thought of doing this by registering the company in the session id but I am not convinced that sessions do not present their own problems.