Django: Forms Queryset - django

I wonder if that is the right approach. I first call queryset=Reward.objects.all() just to change it right after and filter it. However, I couldn't come up with a better solution. Do you have any thoughts on that?
class ClaimRewardForm(forms.ModelForm):
note = forms.CharField(widget=forms.Textarea)
title = forms.ModelChoiceField(queryset=Reward.objects.all())
# note = forms.DropDown()
class Meta:
model = Reward
fields = ['title']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'].queryset = Reward.objects.filter(event=self.initial['event'])

That queryset is never evaluated, because you always replace it on instantiation, so it doesn't really matter what you put there.
One alternative night be to use Reward.objects.none() to indicate that it's never used.

If you should process set of models, try change your approach to FormSets https://docs.djangoproject.com/en/2.0/topics/forms/formsets/
Briefly, FormSets approach seem so: 1. Declare ClaimRewardForm class for single model (Reward in your case) 2. Declare ClaimRewardFormSet for ClaimRewardForm with overriding
class BaseClaimRewardFormSet(BaseModelFormSet):
"""By default, when you create a formset from a model, the formset
will use a queryset that includes all objects in the model"""
def __init__(self, *args, **kwargs):
if 'event' in kwargs.keys():
event = kwargs.pop('event')
else:
event = None
super().__init__(*args, **kwargs)
if event is not None:
self.queryset = Reward.objects.filter(event=event)
else:
self.queryset = Reward.objects.none()
ClaimRewardFormSet = forms.modelformset_factory(Reward, RewardForm,
formset=BaseClaimRewardFormSet)

Related

Initializing variables in init when using Django Rest Framework for list serializers

I would like to have a serializer where one of the fields in the serializer depends on a value created in the init method.
I have tried to do something like the following:
class MySerializer(serializers.ModelSerializer):
the_field_value = serializers.SerializerMethodField()
another_field_value = serializers.SerializerMethodField()
class Meta:
model = MyModel
fields = ('id', 'the_field_value', 'another_field_value')
def get_the_field_value(self, obj):
return "{0} ({1}) {2}".format(obj.name, obj.other_name, self.random_id)
def get_another_field_value(self, obj):
return "{0} ({1}) {2}".format(obj.other_other_name, obj.other_name, self.random_id)
def __init__(self, *args, **kwargs):
super(MySerializer, self).__init__(*args, **kwargs)
self.random_id = randint(1, 5)
The issue is when using a list serializer the random_id is the same for every list item.
How can I specify an init method to be run for each list item?
Updated with a more detailed example.
That's right, because the __init__ will be called just one time when you create an instance, but if you do it:
class MySerializer(serializers.ModelSerializer):
the_field_value = serializers.SerializerMethodField()
class Meta:
model = MyModel
fields = ('id', 'the_field_value')
def get_the_field_value(self, obj):
return "{0} ({1}) {2}".format(obj.name, obj.other_name, randint(1, 5))
you will get the random value on a list. Hope that it helps you.
I think that you want to use the many init method which allows you customize the initialization of a list of objects on a per object basis. It's not very clear from the documentation exactly how it's supposed to be work, but it seems like you can customize the initialization of a list of objects on a per object basis -- perhaps something like this...?
class MySerializer(serializers.ModelSerializer):
# all your fields and get methods...
def random_init(self):
return randint(1,5)
#classmethod
def many_init(cls, *args, **kwargs):
# Instantiate the child serializer.
kwargs['child'] = cls()
# add the random value
kwargs['random_id'] = cls.random_init()
# Instantiate the parent list serializer.
return MySerializer(*args, **kwargs)
def __init__(self, *args, **kwargs):
super(MySerializer, self).__init__(*args, **kwargs)
self.random_id = kwargs.get('random_id', self.random_init() )

Get the current queryset shown in ChangeList from the ModelAdmin class

I need to work in ModelAdmin with the elements shown in the ChangeList, but I don't know how to get the current queryset.
For example, if now the first 100 elements are being shown, I want to work with this set, and if the user pass to the next 100, I want to have the new 100 elements in the set.
Other example is when some user applies some filter.
In my Model Admin I have:
list_display = getListDisplay(qs)
And I want to pass to getListDisplay the current queryset, because depending on it, the list_display will be different.
Is there any current queryset attribute somewhere accessible from ModelAdmin class?
After UPD 1 I cannot make this works:
class YourAdmin(admin.ModelAdmin):
def get_queryset(self):
qs = super(YourAdmin, self).get_queryset()
return qs
def __init__(self, *args, **kwargs):
super(YourAdmin, self).__init__(*args, **kwargs)
qs = self.get_queryset()
Here's a link to the documentation for ModelAdmin.get_queryset()
Edit in response to your comments:
It seems to me that what you want to do is build the list_display dynamically. Django has a get_list_display method for model admin. This method receives the request which you can then pass to get_queryset:
class YourAdmin(admin.ModelAdmin):
def get_list_display(self, request):
qs = self.get_queryset(request)
'''
Now build the list_display as a list or tuple
'''
.
.
return list_display
In django admin, there is a thing called actions. It is described in here and works like this:
class YourAdmin(admin.ModelAdmin):
def make_something(self, request, queryset):
queryset.update(status='p')
actions = ['make_something',]
Maybe queryset is the thing you need
UPD 1: After comment, I understood that you need to modify some attributes of your ModelAdmin ojbect. So you can do that right after initialization:
class YourAdmin(admin.ModelAdmin):
def __init__(self, *args, **kwargs):
super(YourAdmin, self).__init__(*args, **kwargs)
qs = self.getquery_set()
// modify your list_display depending on qs
self.list_display = ['some','fileds','here']
Some third-party apps like xadmin allow you to change list_display on the fly, but django doesn't have this feature by default.

Dynamically include or exclude Serializer class fields

In my User profile model I've included a show_email field explicitly. So, to add this feature to my API, the UserSerializer class looks like this:
class UserSerializer(serializers.ModelSerializer):
email = serializers.SerializerMethodField('show_email')
def show_email(self, user):
return user.email if user.show_email else None
class Meta:
model = django.contrib.auth.get_user_model()
fields = ("username", "first_name", "last_name", "email")
But I don't really like it. I think it would be a lot cleaner if the field email would be completely excluded from the serializer output it show_email is False, instead showing that ugly "email": null thing.
How could I do that?
You could do this in your API view by overriding the method returning the response, i.e. the "verb" of the API view. For example, in a ListAPIView you would override get():
class UserList(generics.ListAPIView):
model = django.contrib.auth.get_user_model()
serializer_class = UserSerializer
def get(self, request, *args, **kwargs):
response = super(UserList, self).get(request, *args, **kwargs)
for result in response.data['results']:
if result['email'] is None:
result.pop('email')
return response
You would probably want to add some more checking for attributes, but that's the gist of how it could be done. Also, I would add that removing fields from some results may cause issues for the consuming application if it expects them to be present for all records.
This answer comes late but for future google searches: there is an example in the documentation about Dynamically modifying fields.
So, by passing an argument to the serializer, you control whether or not a field is processed:
serializer = MyPostSerializer(posts, show_email=permissions)
and then in the init function in the serializer you can do something like:
class MyPostSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
show_email = kwargs.pop('show_email', None)
# Instantiate the superclass normally
super(DeviceSerializer, self).__init__(*args, **kwargs)
if not show_email:
self.fields.pop("show_email")
Now the show_email field will be ignored by the serializer.
You could override restore_fields method on serializer. Here in restore_fields method you can modify list of fields - serializer.fields - pop, push or modify any of the fields.
eg: Field workspace is read_only when action is not 'create'
class MyPostSerializer(ModelSerializer):
def restore_fields(self, data, files):
if (self.context.get('view').action != 'create'):
self.fields.get('workspace').read_only=True
return super(MyPostSerializer, self).restore_fields(data, files)
class Meta:
model = MyPost
fields = ('id', 'name', 'workspace')
This might be of help...
To dynamically include or exclude a field in an API request, the modification of
#stackedUser's response below should do:
class AirtimePurchaseSerializer(serializers.Serializer):
def __init__(self, *args, **kwargs):
try:
phone = kwargs['data']['res_phone_number']
except KeyError:
phone = None
# Instantiate the superclass normally
super(AirtimePurchaseSerializer, self).__init__(*args, **kwargs)
if not phone:
self.fields.pop("res_phone_number")
res_phone_number = serializers.CharField(max_length=16, allow_null=False)

django modelform and additional fields and their order

I have a modelform and im creating additional fields (that do not exist in model) for its form.
I know you can reorder the fields in modelform like it says in the docs.
But the problem is - i want the additional fields to be rendered BEFORE the other fields.
Is it possible to somehow reorder the fields of the form before rendering? How does form object keep track of the order of its fields anyway?
Alan
No matter. It seems i found answer already and this seems to do the trick, since i have added 2 additional fields:
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.build_fields()
l = len(self.fields.keyOrder)
for i in range(0,2):
f = self.fields.keyOrder[l-1]
self.fields.keyOrder.pop(l-1)
self.fields.keyOrder.insert(0, f)
This above was my initial fix. Later on i found out that it did not cut any more. Then i did this :
class AlertForm(forms.ModelForm):
class Meta:
model = Message
fields = model_fields
def __init__(self, *args, **kwargs):
super(AlertForm, self).__init__(*args, **kwargs)
self.build_fields()
newKeyOrder = []
newKeyOrder.append('field_that_had_to_be_first')
if typechange:
newKeyOrder.append('field_thats_sometimes_necessary')
newKeyOrder += model_fields
self.fields.keyOrder = newKeyOrder
The solutions above no longer works with django 2 (I don't know since when)...
But now, there's an ordered dict fields property on ModelForm that we can use to reorder the fields...
class MyForm(forms.ModelForm):
class Meta:
fields = ['model_field1', 'model_field2']
model = MyModel
extra_field = forms.CharField()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for k in self._meta.fields:
self.fields.move_to_end(k)
The fields attribute of your ModelForm`s Meta class define which fields to show and in which order.
Use the fields attribute of the ModelForm's inner Meta class. This attribute, if given, should be a list of field names to include in the form. The order in which the fields names are specified in that list is respected when the form renders them.

Django - populate a MultipleChoiceField via request.user.somechoice_set.all()

Is there a more efficient, or cleaner way to do the following?
class SpamForm(ModelForm):
some_choices = fields.MultipleChoiceField()
def __init__(self, *args, **kwargs):
super(SpamForm, self).__init__(*args, **kwargs)
self.fields['some_choices'].choices = [[choice.pk, choice.description] for choice in self.instance.user.somechoice_set.all()]
class Meta:
model = Spam
This is to populate the new form with choices that pertain to the current request.user (which is set on the instance that's passed into the form). Is there a better way?
Use a ModelMultipleChoiceField in the form and set its queryset in __init__().