Django combine two fields data into a queryset - django

I have a model say Club where we have fields like:
manager = models.ForeignKey(Users, related_name="return_manager", on_delete=models.CASCADE)
members = models.ManyToManyField(Users, related_name="return_members", blank=True)
Now I want to create a drop down in a form where I can add both the manager and members to it. I tried making two requests for Club.objects.filter(pk=mypk).members.all() and Club.objects.filter(pk=mypk).manager. I tried chain function and using '| ' operator but none worked. I think the manager is a single User and not a queryset, that is what the main problem is. Any workarounds?

One possible way getting all of the information together involves modifying your form choices.
In your view you would need to pass the choices along as context to your form.
def my_view(request, club_pk):
context = {}
context.update({
"manager": Club.objects.get(pk=club_pk).manager,
"members": Club.objects.get(pk=club_pk).members.all()
}
form = MyForm(request.POST or None, request=request, context=context)
In your form, you would need to modify the __init__ method to update your choices like so:
class MyForm(forms.Form):
all_club_members = forms.ChoiceField('Manager + Members', required=True)
def __init__(self, *args, **kwargs):
self.context = kwargs.pop('context', None)
super(MyForm, self).__init__(*args, **kwargs)
manager_tuple = [(self.context['manager'].id, self.context['manager'].display_name)]
members_tuples = [(member.id, member.display_name) for member in self.context['members']
self.fields['all_club_members'].choices = manager_tuple + members_tuples

Try this:
manager = [Club.objects.filter(pk=mypk).manager]
members = Club.objects.filter(pk=mypk).members.all()
userlist = list(manager) + list(members)
return Users.objects.filter(pk__in=userlist)
Should create a queryset of all users

Related

How to access run-time request.session values in forms.py definition?

I have an inventory management app that will be serving multiple locations (called contexts in my app). When a user is logged in, their current context is stored as a value in request.sessions.
I would like users to only be able to browse and retrieve records for their own location.
I've been trying to this by filtering the queryset that is called in the form definition to populate the select dropdown, i.e.
referenced_catalog = forms.ModelChoiceField(
queryset=Inventory_unit_catalog.objects.all().filter(parent_business_unit_context_id=user_context_id),
I've tried implementing several different (but similar) approaches from various SO posts, that involve defining an init block to the form, such as:
class InventoryStockAddForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user_context_id = kwargs.pop('user_context_id', None)
super(InventoryStockAddForm, self).__init__(*args, **kwargs)
name = forms.CharField(max_length=96,widget=forms.TextInput(),required=True)
referenced_catalog = forms.ModelChoiceField(
queryset = Inventory_unit_catalog.objects.all().filter(parent_business_unit_context_id=self.user_context_id),
label = u"",
widget = ModelSelect2Widget(
model=Inventory_unit_catalog,
search_fields=['name__icontains'],
attrs={'data-placeholder': 'Select catalog...', 'data-width': '35em'},
required=False))
class Meta():
model = Inventory_unit_stock
fields = ('name',)
(Different SO answers had one way or the other.)
Then in views.py:
user_context_id = request.session.get('user_context_id')
...
add_form = InventoryStockAddForm(user_context_id=user_context_id)
I've even tried using the SessionStore per https://djangobook.com/using-sessions-views-2/:
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
s = SessionStore()
user_context_id = s['user_context_id']
but it always fails at the moment the forms.py is updated as Django validates the code and cannot find a key value at the moment of validation.
Any advice would be appreciated, thanks!
You can't access self.user_context_id inside referenced_catalog = forms.ModelChoiceField(...) - that code runs when the module is loaded, not when the form is initialised.
Instead, you should set the queryset inside the __init__ method.
class InventoryStockAddForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user_context_id = kwargs.pop('user_context_id', None)
super(InventoryStockAddForm, self).__init__(*args, **kwargs)
self.fields['referenced_catalog'].queryset = Inventory_unit_catalog.objects.all().filter(parent_business_unit_context_id=self.user_context_id)
referenced_catalog = forms.ModelChoiceField(
queryset = Inventory_unit_catalog.objects.none(),
label = u"",
widget = ModelSelect2Widget(
model=Inventory_unit_catalog,
search_fields=['name__icontains'],
attrs={'data-placeholder': 'Select catalog...', 'data-width': '35em'},
required=False))

Getting the value of a forms.ChoiceField instead of the label, from the view

I have a FormView from which I'd like to do a bit of form processing.
Within the form_valid() I'm trying to obtain the submitted form's values in order to instantiate a different object.
When I get the form.cleaned_data however, I am returned a dictionary of {'form field name' : 'choice label'}.
I'd like to get the value corresponding to the choice label.
Here is the FormView--the get_form_kwargs bit is simple passing custom choices to the view:
class RequestView(FormView):
form_class = RequestForm
template_name = 'profile/request.html'
success_url = '/'
def get_form_kwargs(self, *args, **kwargs):
requester_obj = Profile.objects.get(
user__username=self.request.user)
accepter_obj = Profile.objects.get(
user__username=self.kwargs.get('username'))
r_favorite_set = requester_obj.get_favorite()
a_favorite_set = accepter_obj.get_favorite()
kwargs = super().get_form_kwargs()
kwargs['your_favorite'] = r_favorite_set
kwargs['their_favorite'] = a_favorite_set
return kwargs
def form_valid(self, form):
super(RequestView, self).form_valid(form)
# I've tried get_FOO_display() extra instance method
print(form.get_your_favorite_display())
# The following will return the choice label
print(form.cleaned_data.get('your_favorite'))
Favorites.objects.create(#need form choice selections)
return redirect(self.get_success_url())
Code explanation: Within the form_valid I hope to create a different object using the selection from the submitted form.
So how do I get the submitted choice instead of the label? Is this even the proper place to do this?
edit: The Form:
class RequestForm(forms.Form):
your_favorite = forms.ChoiceField(
choices=[],
widget=RadioSelect,
required=True,
label="What would you like to exchange?"
)
their_favorite = forms.ChoiceField(
widget=RadioSelect,
required=False,
)
def __init__(self, *args, **kwargs):
your_choices = kwargs.pop('your_favorite')
their_choices = kwargs.pop('their_favorite')
super().__init__(*args, **kwargs)
self.fields['your_favorite'].choices = your_choices
self.fields['their_favorite'].choices = their_choices
edit2:
def get_favorite(self):
return (('a', self.fave1), ('b', self.fave2), ('c', self.fave3))
edit3:
...So I could've just done this all along.
def get_favorite(self):
return ((self.fave1, self.fave1), (self.fave2, self.fave2), (self.fave3, self.fave3))
BUT this causes some really funky behavior in the form. For some reason this causes a radio to be selected by default with every GET request to the form view. It will select the third choice to be selected if all the options are "None", or the first option to be selected if only the first is "None".
Anyway, this may be a question for a separate post.

Django: Accessing parent object in new linline object

I have been combing through the internet for quite some while without finding any solution to this problem.
What I am trying to do...
I have the following models:
class TrackingEventType(models.Model):
required_previous_event = models.ForeignKey(TrackingEventType)
class TrackingEvent(models.Model):
tracking = models.ForeignKey(Tracking)
class Tracking(models.Model):
last_event = models.ForeignKey(TrackingEvent)
Now the main model is Tracking, so my admin for Tracking looks like this:
class TrackingEventInline(admin.TabularInline):
model = TrackingEvent
extra = 0
class TrackingAdmin(admin.ModelAdmin):
inlines = [TrackingEventInline]
That's it for the current setup.
Now my quest:
In the TrackingAdmin, when I add new TrackingEvent inlines, I want to limit the options of TrackingEventType to onlye those, that are allowed to follow on the last TrackingEvent of the Tracking. (Tracking.last_event == TrackingEventType.required_previous_event).
For this, I would need to be able to access the related Tracking on the InlineTrackingEvent, to access the last_event and filter the options for TrackingEventType accordingly.
So I found this: Accessing parent model instance from modelform of admin inline, but when I set up TrackingEventInline accordingly:
class MyFormSet(forms.BaseInlineFormSet):
def _construct_form(self, i, **kwargs):
kwargs['parent_object'] = self.instance
print self.instance
return super(MyFormSet, self)._construct_form(i, **kwargs)
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
print kwargs
self.parent_object = kwargs.pop('parent_object')
super(MyForm, self).__init__(*args, **kwargs)
class TrackingEventInline(admin.TabularInline):
form = MyForm
formset = MyFormSet
model = TrackingEvent
extra = 0
I get a KeyError at /admin/.../tracking/2/change/ 'parent_object' from self.parent_object = kwargs.pop('parent_object')
Does anyone know how to solve this? Am I approaching the problem the wrong way? I guess this would be pretty easy in a custom form in the frontend, but I really want to use the admin, because the whole application is built to be used from the admin, and it would be a hell lot of work to build a custom admin interface just because of this problem :)
Ok, so posting on StackOverflow is always helping to get the problem straight. I was able to put together a solution that works for me.
It includes defining my own Form in a outer function, as well as defining two InlineAdmin objects for TrackingEvent (one for update / edit, one just for insert).
Here's the code:
def create_trackingevent_form(tracking):
"""
"""
class TrackingEventForm(forms.ModelForm):
"""
Form for Tracking Event Inline
"""
def clean(self):
"""
May not be needed anymore, since event type choices are limited when creating new event.
"""
next_eventtype = self.cleaned_data['event_type']
tracking = self.cleaned_data['tracking']
# get last event, this also ensures last_event gets updated everytime the change form for TrackingEvent is loaded
last_eventtype = tracking.set_last_event()
if last_eventtype:
last_eventtype = last_eventtype.event_type
pk = self.instance.pk
insert = pk == None
# check if the event is updated or newly created
if insert:
if next_eventtype.required_previous_event == last_eventtype:
pass
else:
raise forms.ValidationError('"{}" requires "{}" as last event, "{}" found. Possible next events: {}'.format(
next_eventtype,
next_eventtype.required_previous_event,
last_eventtype,
'"%s" ' % ', '.join(map(str, [x.name for x in tracking.next_tracking_eventtype_options()]))
)
)
else:
pass
return self.cleaned_data
def __init__(self, *args, **kwargs):
# You can use the outer function's 'tracking' here
self.parent_object = tracking
super(TrackingEventForm, self).__init__(*args, **kwargs)
self.fields['event_type'].queryset = tracking.next_tracking_eventtype_options()
#self.fields['event_type'].limit_choices_to = tracking.next_tracking_eventtype_options()
return TrackingEventForm
class TrackingEventInline(admin.TabularInline):
#form = MyForm
#formset = MyFormSet
model = TrackingEvent
extra = 0
#readonly_fields = ['datetime', 'event_type', 'note']
def has_add_permission(self, request):
return False
class AddTrackingEventInline(admin.TabularInline):
model = TrackingEvent
extra = 0
def has_change_permission(self, request, obj=None):
return False
def queryset(self, request):
return super(AddTrackingEventInline, self).queryset(request).none()
def get_formset(self, request, obj=None, **kwargs):
if obj:
self.form = create_trackingevent_form(obj)
return super(AddTrackingEventInline, self).get_formset(request, obj, **kwargs)
I hope this helps other people with the same problem.. Some credit to the Stack Overflow threads that helped me come up with this:
Prepopulating inlines based on the parent model in the Django Admin
Limit foreign key choices in select in an inline form in admin
https://docs.djangoproject.com/en/1.9/ref/models/instances/#django.db.models.Model.clean_fields
Please do not hesitate to ask questions if you have any

Django ChoiceField initial setup not working

I have ModelForm where i use Django Forms.ChoiceField. Writing the value to the database works. But when i open the url, the dropdown list is not showing the previously selected value as selected value.
I tried setting initial=value, but it's not working as well.
class GameForm(forms.ModelForm):
gameCode = forms.ChoiceField(required=False)
def __init__(self, *args, **kwargs):
obj = AllGameCodes.objects.filter(game=game)
choices = []
choices.append(('', '-----------'))
for i in obj:
choices.append((i.code,i.description))
self.fields['gameCode'].choices = choices
in views.py,
game = games.objects.get(id=1)
form = GameForm(request.POST, initial={'code':game.code}
You must take game variable from kwargs. Also using ModelChoicefield may ease your solution
def __init__(self, *args, **kwargs):
super(GameForm, self).__init__(*args, **kwargs)
_game = kwargs.get("game"):
if _game:
self.fields['gameCode'] = ModelChoiceField(queryset=AllGameCodes.objects.filter(game=_game), required=False)
For future reference, you may use form = GameForm(instance=game) to load the form with the model data and write new data to that model.
Also instead of overwriting the form class, you can alter fields in your view
#views.py
game = games.objects.get(id=1)
form = GameForm(request.POST, instance=game)
form.fields['gameCode'].queryset = AllGameCodes.objects.filter(game=game)

ModelChoiceField in forms.Form won't validate if queryset is overridden

I have a django ModelChoiceField that won't validate if I override the queryset.
class PersonalNote(forms.Form):
tile = ModelChoiceField(queryset=Tile.objects.none())
note = forms.CharField()
form = PersonalNote()
form.fields['tile'].queryset = Tile.objects.filter(section__xxx=yyy)
The form.is_valid() error is: "Select a valid choice. That choice is not one of the available choices".
If Tile.objects.none() is replaced with Tile.objects.all() it validates, but loads far too much data from the database. I've also tried:
class PersonalNote(forms.Form):
tile = ModelChoiceField(queryset=Tile.objects.none())
note = forms.CharField()
def __init__(self, *args, **kwargs):
yyy = kwargs.pop('yyy', None)
super(PersonalNote, self).__init__(*args, **kwargs)
if yyy:
self.fields['tile'].queryset = Tile.objects.filter(section__xxx=yyy)
What might be wrong here? Note the real application also overrides the label, but that does not seem to be a factor here:
class ModelChoiceField2(forms.ModelChoiceField):
def label_from_instance(self, obj):
assert isinstance(obj,Tile)
return obj.child_title()
After 2 hours I found the solution. Because you specified a queryset of none in the class definition, when you instantiate that PersonalNote(request.POST) to be validated it is referenceing a null query set
class PersonalNote(forms.Form):
tile = ModelChoiceField(queryset=Tile.objects.none())
note = forms.CharField()
To fix this, when you create your form based on a POST request be sure to overwrite your queryset AGAIN before you check is_valid()
def some_view_def(request):
form = PersonalNote(request.POST)
**form.fields['tile'].queryset = Tile.objects.filter(section__xxx=yyy)**
if form.is_valid():
#Do whatever it is
When you pass an empty queryset to ModelChoiceField you're saying that nothing will be valid for that field. Perhaps you could filter the queryset so there aren't too many options.
I also had this problem. The idea is to dynamically change the queryset of a ModelChoiceField based on a condition (in my case it was a filter made by another ModelChoiceField).
So, having the next model as example:
class FilterModel(models.Model):
name = models.CharField()
class FooModel(models.Model):
filter_field = models.ForeignKey(FilterModel)
name = models.CharField()
class MyModel(models.Model):
foo_field = models.ForeignKey(FooModel)
As you can see, MyModel has a foreign key with FooModel, but not with FilterModel. So, in order to filter the FooModel options, I added a new ModelChoiceField on my form:
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, *args, **kwargs):
# your code here
self.fields['my_filter_field'] = forms.ModelChoiceField(FilterModel, initial=my_filter_field_selected)
self.fields['my_filter_field'].queryset = FilterModel.objects.all()
Then, on your Front-End you can use Ajax to load the options of foo_field, based on the selected value of my_filter_field. At this point everyting should be working. But, when the form is loaded, it will bring all the posible options from FooModel. To avoid this, you need to dynamically change the queryset of foo_field.
On my form view, I passed a new argument to MyForm:
id_filter_field = request.POST.get('my_filter_field', None)
form = MyForm(data=request.POST, id_filter_field=id_filter_field)
Now, you can use that argument on MyForm to change the queryset:
class MyForm(forms.ModelForm):
# your code here
def __init__(self, *args, **kwargs):
self.id_filter_field = kwargs.pop('id_filter_field', None)
# your code here
if self.id_filter_field:
self.fields['foo_field'].queryset = FooModel.objects.filter(filter_field_id=self.id_filter_field)
else:
self.fields['foo_field'].queryset = FooModel.objects.none()