Django - How to exclude already selected object from queryset in form? - python

I am seeking an advice on how to approach the following problem:
I am having these models: Event > EventAttendees > EventRoster.
Every event has attendees (users) which I would like to assign to specific positions (attributes of EventRoster) for that particular Event.
Example: -Event(Football/Soccer game), attendees: (Bob, John, Mike, Steve), EventRoster: (Bob = goaltender, John = left wing, Mike = center, Steve = defence)
Idea is to have a form for event, where anyone from attendees can be assigned to any of the positions. Each form field would represent a position, where attendee can be selected, if that attendee was not already selected for different position.
My problem is, that I am selecting attendees from EventAttendees.objects.all() or .filter(event__pk=pk) without updating this queryset to exclude attendees that are already selected for some other position.
What I am aiming for is to exclude attendee from queryset if that attendee is already selected in form field (position). Basically to have empty queryset once all attendees are assigned to all positions.
What would be the best way to approach this? I have found similarities in so called chained fields, but I guess these are not applicable in this scenario. Probably I will not be able to this without ajax as field values on form will not change unless form is submitted.
Thank you in advance for your support!
class Event(models.Model):
name = models.CharField(max_length=30, blank=True, null=True)
attendees = models.ManyToManyField(User, through='EventAttendees')
class EventAttendees(models.Model):
user = models.ForeignKey(User, related_name='user_event')
event = models.ForeignKey(Event, related_name='ice')
def __str__(self):
return self.user.username
class Meta:
unique_together = ('user','event')
class EventRoster(models.Model):
event = models.ForeignKey(Event, related_name='event_roster')
goalie = models.CharField(max_length=20, blank=True, null=True, unique=True)
center = models.CharField(max_length=20, blank=True, null=True, unique=True)
left_wing = models.CharField(max_length=20, blank=True, null=True, unique=True)
defensemen = models.CharField(max_length=20, blank=True, null=True, unique=True)
class RosterForm(forms.ModelForm):
goalie = forms.ModelChoiceField(queryset=EventAttendees.objects.none())
center = forms.ModelChoiceField(queryset=EventAttendees.objects.none())
left_wing = forms.ModelChoiceField(queryset=EventAttendees.objects.none())
defensemen = forms.ModelChoiceField(queryset=EventAttendees.objects.none())
def __init__(self, *args, **kwargs):
super(RosterForm, self).__init__(*args, **kwargs)
self.fields['goalie'].queryset = EventAttendees.objects.filter(event__pk=50).distinct()
self.fields['center'].queryset = EventAttendees.objects.filter(event__pk=50).distinct()
self.fields['left_wing'].queryset = EventAttendees.objects.filter(event__pk=50).distinct()
self.fields['defensemen'].queryset = EventAttendees.objects.filter(event__pk=50).distinct()
class Meta:
model = EventRoster
fields = ['goalie','center','left_wing','defensemen']

I cannot really undestand the logic except i see the whole code i.e views and forms but i think the exclude() function would come in handy when querying the model

Related

How to assign default value of the model based on the value of ForeignKey

I have the Account model were I store information about preferred units.
However I also want to allow user to change the units for particular exercise which by default should be Account.units.
Here are my models:
class Account(models.Model):
"""Model to store user's data and preferences."""
UNIT_CHOICES = [
('metric', 'Metric'),
('imperial', 'Imperial')
]
uuid = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False)
created = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, blank=False)
units = models.CharField(max_length=255, choices=UNIT_CHOICES, default=UNIT_CHOICES[0], null=False, blank=False)
weight_metric = models.FloatField(null=True, blank=True)
height_metric = models.FloatField(null=True, blank=True)
weight_imperial = models.FloatField(null=True, blank=True)
height_imperial = models.FloatField(null=True, blank=True)
def __str__(self):
return self.owner.email
class CustomExercise(models.Model):
UNIT_CHOICES = [
('metric', 'Metric'),
('imperial', 'Imperial')
]
uuid = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False)
created = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(Account, on_delete=models.CASCADE, null=False, blank=False)
preferred_units = models.CharField(max_length=255, choices=UNIT_CHOICES, default=owner.units, null=False, blank=False) # <- throws an error that "ForeignKey doesn't have units attribute."
name = models.CharField(max_length=255, null=False, blank=False)
measure_time = models.BooleanField(default=False)
measure_distance = models.BooleanField(default=False)
measure_weight = models.BooleanField(default=False)
measure_reps = models.BooleanField(default=False)
def __str__(self):
return f'{self.owner}:{self.name}'
As posted in code sample I tried to get that default value from ForeignKey, which not unexpectedly did not work out.
So my question is: what is the correct solution to implement this kind of feature?
I would not recommend storing duplicate values accross multiple models. You can easily access that value through a property method:
class CustomExercise(models.Model):
... # remove preferred_units field from model
#property
def preferred_units(self):
return self.owner.unit
Although you can not use it in queryset directly, still you can annotate the 'owner__unit' field in queryset or filter by it:
q = CustomExcercise.objects.annotate(preferred_units=F('owner__unit')).filter(preferred_units = 'kg')
q.values()
Displaying the value in Adminsite:
class CustomExerciseAdmin(admin.ModelAdmin):
fields = (..., 'preferred_units')
readonly_fields = ['preferred_units']
Two ways come to mind: overriding the model's save method or by using a pre_save signal. I would try the first one and if it doesn't work then the second one. The reason is that signals are notoriously difficult to debug so if you have alternatives you should always leave them as a last resort.
Ok so, I think this should work:
def save(self, *args, **kwargs):
self.preferred_units = self.owner.units
super(CustomExercise, self).save(*args, **kwargs
Otherwise:
#receiver(pre_save, sender=CustomExercise)
def assign_unit(sender, instance, **kwargs):
instance.preferred_units = instance.owner.units
The convention is to store your signals in signals.py in your app. Make sure to "activate" them from apps.py or they won't work. Here the docs.

Combine Data of Two Tables in single DropDown using Django

Problem: I have Two Tables Users and Users Group and in front end on
request of that particular page need to send all data from both the
table together, as there is specific drop down to show them both, and after done with the operation of that page data will get back in POST request (current models structures is given below), i am not getting how do i make connection in all these three tables so that it will get managed, Please let me know.
Model: User.py
class Users(AbstractBaseUser):
vendor_name = models.ForeignKey(Vendor, on_delete=models.CASCADE, default=None, null=True)
username = models.CharField(max_length=100, verbose_name="username", unique=True)
password = models.CharField(max_length=100)
created_by = models.DateField(verbose_name="created_by", auto_now_add=True)
USERNAME_FIELD = "username"
REQUIRED_FIELDS = ['password', 'hardware_id']
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
is_role_vendor = models.BooleanField(default=False)
is_role_customer = models.BooleanField(default=True)
def __str__(self):
return self.username
objects = UserManager()
Model: UserGroup.py
class UserGroup(models.Model):
vendor_id = models.ForeignKey(Vendor, on_delete=models.CASCADE, default=None, null=True)
id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=100)
description = models.CharField(max_length=1000)
users = models.ManyToManyField(Users)
def __str__(self):
return self.name
Model: Rules.py
class Rules(models.Model):
vendor_id = models.ForeignKey(Vendor, on_delete=models.CASCADE, default=None, null=True)
id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=100)
description = models.CharField(max_length=1000)
# Here i need to mention the code for field where i can store the combined value of User and UserGroup [selected from the dropdown].
def __str__(self):
return self.name
Need Solution:
How do i code in View to fetch the data of Two tables to send them for DropDown. {GET Request}
How will i store the values for the same together in Rules Table { As i said DropDown consist both values and can be selected all
together. }
The Structure of the Model {with the required Changes}.
there is no out of the box solution for that. I can advise to seprate this dropdown into two. First with UserGroup, second with User. You can fill user dropdown based on selected UserGroup with ajax or htmx -> htmx value-select
In your model Rules (should be Rule)
add fields:
user = models.ForeignKey(Users, on_delete=models.CASCADE)
group = models.ForeignKey(UserGroup, on_delete=models.CASCADE)
if there can be only one rule per Users(this should also be User)/UserGroup add unique_toigether to model Rules:
unique_together = ['user', 'group']
django docs unique_together

Django-filter how to show only some objects on dropdown?

My site simply works like this: every Manager can have some SubManagers, those SubManagers can have some Agents (so the Agents are indirectly related to the Manager, see models.py to understand better the relations between them). I want to show in the Manager's profile page (see views.py) all the MembershipCard created by his/her related Agents. I'm trying to implement a filter to search, for example, cards created by a specific Agent, i'm able to do this but i would like to show in the dropdown only the Agents related to the Manager, the dropdown list now shows all Agents in the database
models.py
class StandardProfile(models.Model):
name = models.CharField(max_length=200, null=True)
surname = models.CharField(max_length=200, null=True)
phone_number = models.CharField(max_length=200, null=True)
class Meta:
abstract = True
class Manager(StandardProfile):
user = models.OneToOneField(User, null=True, on_delete=models.CASCADE)
class SubManager(StandardProfile):
user = models.OneToOneField(User, null=True, on_delete=models.CASCADE)
manager = models.ForeignKey(Capo, null=True, on_delete = models.SET_NULL)
class Agent(StandardProfile):
user = models.OneToOneField(User, null=True, on_delete=models.CASCADE)
manager = models.ForeignKey(Manager, null=True, on_delete = models.SET_NULL)
subManager = models.ForeignKey(SubManager, null=True, blank=True, on_delete = models.SET_NULL)
class MembershipCard(models.Model):
agent = models.ForeignKey(Agent, null=True,blank=True, on_delete = models.SET_NULL)
client = models.ForeignKey(Client, null=True,blank=True, on_delete = models.SET_NULL)
creation_date = models.DateTimeField(auto_now_add=True, null=True)
activation_date = models.DateTimeField(null=True,blank=True)
expiration_date = models.DateTimeField(null=True,blank=True)
views.py
#login_required(login_url='login')
def profilePage(request, pk): #www.mysite.com/profilePage/<pk>
user = User.objects.get(id=pk) #getting the user from <pk>
cards = MembershipCard.objects.filter(agent__manager=user.manager)
myFilter = MembershipCardFilter(request.GET,queryset=cards,user=user)
cards = myFilter.qs
#page_obj is used for Pagination, and contains the cards, i removed this part of code for better readability, can add it if needed
context = {'page_obj': page_obj,"user": user,"myFilter":myFilter}
return render(request, 'polls/profilePage.html',context)
filters.py
class MembershipCardFilter(django_filters.FilterSet):
class Meta:
model = MembershipCard
fields = ['agent','agent__subManager']
exclude = ['creation_date']
By reading answers to similar questions i think i have to modify the __init__ method in the CardFilter class, i've tried to adapt some answers to my case but it didn't work for some reasons . Any anser/comment is appreciated!
PS: I don't know if the title is clear, feel free to suggest a better one
You can try feeding the agent dropdown during init like (not tested!):
class MembershipCardFilter(django_filters.FilterSet):
agent= django_filters.ModelChoiceFilter(
queryset=Agent.objects.none(),
)
class Meta:
model = MembershipCard
fields = ['agent','agent__subManager']
exclude = ['creation_date']
def __init__(self, *args, **kwargs):
user = kwargs.get("user")
agents = Agent.objects.filter(manager__user=user)
super().__init__(*args, **kwargs)
self.filters["agent"].queryset = agents

Field 'id' expected a number but got <Listing: Ps4 Pro>

It's my first time creating a Django website with models, and in my first attempt to insert data into my table I'm getting this error.
My models are as follows:
class User(AbstractUser):
pass
#https://docs.djangoproject.com/en/3.1/topics/auth/default/
class Listing(models.Model):
listingID = models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="listID")
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, related_name="myListing", null=True)
watchers = models.ManyToManyField(User, blank=True, related_name="watchlist")
title = models.CharField(max_length=30)
description = models.TextField()
creation_date = models.DateField(auto_now=True)
img_url = models.URLField()
active = models.BooleanField(default=True)
def __str__(self):
return f"{self.title}"
class Bid(models.Model):
listing = models.ForeignKey(Listing, on_delete=models.SET_NULL, related_name="bidsMadeOnMe", null=True, blank=True)
user = models.ForeignKey(User, on_delete=models.SET_NULL, related_name="myBids", null=True)
price = models.FloatField()
creation_date = models.DateField(auto_now=True, null=True)
def __str__(self):
return f"Bid={self.price}"
and the view that handles the form submission is this one:
#login_required
def create_listing(request):
if request.method == "POST":
user = User.objects.get(username=request.user.username)
l = Listing(created_by=user,
title=request.POST["title"],
description=request.POST["desc"],
# https://stackoverflow.com/questions/12176585/handling-dates-over-request-get
creation_date=models.DateTimeField(auto_now_add=True),
img_url=request.POST["image_url"]
)
l.save()
b = Bid(l,
user,
request.POST["initial_bid"],
models.DateTimeField(auto_now_add=True)
)
b.save()
return render(request, "auctions/index.html")
I know the problem is the way I'm adding the data but I can't fix it. Can someone give me some light?
Your problem (well, several actually) is this:
b = Bid(l, user, request.POST["initial_bid"], models.DateTimeField(auto_now_add=True))
You're constructing a model instance by positional arguments instead of keyword arguments. This can be done, but then the invisible "id" column that has been added to the Bid model, is the first argument.
In Django we never construct models like that, but always use keyword arguments, so we're not depending on field order:
b = Bid(listing=l, user=user, ...))
Once you're solved that, your next problem is the date field.
Don't assign fields to model instances. Fields are class declarations, they don't belong on instances. Fields describe on a class (= a Model), what kind data to expect. On the instance, you assign that data.
In this case, your definition for the field is wrong on the model and on the instance you shouldn't even assign it - it will be automatically filled.
Overall, it feels like you haven't gone through Django's tutorial or did not fully understand the concepts. I suggest you go through it.

Many-to-many relation and form with extra field

I am struggling with Many-to-Many model and form. What is the best way to show extra level field when creating a new Person with skill. I am going also to edit it later.
I am trying to achieve an effect in forms like that:
[input text] name
[select] skill name (there are choices in the model)
[input text] level of the skill
I tried things which people were suggesting but I couldn't make it to work. For example inlineformset + through.
Later on, I would like also to give the user a chance to add multiple sets of skill+level, maybe it is worth to think about it in advance?
My models and form:
class PersonSkill(models.Model):
person = models.ForeignKey('Person', on_delete=models.CASCADE)
skill = models.ForeignKey('Skill', on_delete=models.CASCADE)
level = models.CharField(max_length=50, choices=[(1, 'working'),
(2, 'advanced'),
(3, 'champion')], null=True, blank=True)
class Skill(models.Model):
name = models.CharField(max_length=50, null=True, blank=True)
def __str__(self):
return self.name
class Person(models.Model):
name = models.CharField(max_length=30, null=True, blank=True)
skill = models.ManyToManyField('Skill', through='PersonSkill', blank=True)
def __str__(self):
return self.name
class PersonForm(forms.ModelForm):
class Meta:
model = Person
fields = ('name',)

Categories

Resources