Django Form fields changing after page refresh - django

The first time I open this ModelAdmin's /add page, all the fields of the ServiceProvider model are displayed, although I specified with self.fields the fields that should be displayed.
When pressing F5 to reload the page, the unsolicited fields do not appear. I suspected cache, but disabling the caches did not cause changes. The problem with loading all fields is that it also does some heavy querying related to those fields.
class ServiceProviderAdmin(admin.ModelAdmin):
...
def get_form(self, request, obj=None, **kwargs):
self.fields = (
"name_registration",
"name_social",
"nome_usual",
("gender","document"),
"department",
"department_aditionals",
"picture",
"active",
)
if request.user.has_perm("rh.can_edit_secondary_mail"):
self.fields = self.fields + ("email_secondary",)
self.form = ServiceProviderFormFactory(request.user)
return super().get_form(request, obj=obj, **kwargs)
def ServiceProviderFormFactory(user):
class ServiceProviderForm(forms.ModelForm):
...
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
...
class Meta:
model = ServiceProvider
exclude = ("",)

I managed to resolve this. The ModelAdmin fields were being set in the get_form method, but it seems that on first access, Django fetches the list of fields before getting to the get_form method.
As the list of fields was not yet defined, it got all the fields related to the model.
Changing the fields definition from get_form to get_fields solved the problem:
class ServiceProviderAdmin(admin.ModelAdmin):
...
def get_fields(self, request, obj=None):
fields = (
"name_registration",
"name_social",
"nome_usual",
("gender","document"),
"department",
"department_aditionals",
"picture",
"active",
)
if request.user.has_perm("rh.can_edit_secondary_mail"):
fields = fields + ("email_secondary",)
return fields

Related

Django-Admin: how to use one field for both inlinemodel and model

Hello i am working in django admin panel, i am making 2 models
workflow, and workflow stages, both has filed company
workflow is inline inside of workflow
my question is how to make all of the workflow stages uses the same company field up in the workflow.
class WorkflowStageInline(admin.TabularInline):
model = WorkflowStage
extra = 7
class WorkflowAdmin(admin.ModelAdmin):
inlines = [WorkflowStageInline, ]
list_display = ('id', 'company', 'is_template')
list_display_links = ('id', 'company')
Add these two methods to your WorkflowStageInline class:
def get_formset(self, request, obj=None, **kwargs):
self.parent_obj = obj
return super().get_formset(request, obj, **kwargs)
def formfield_for_dbfield(self, db_field, request, **kwargs):
field = super().formfield_for_dbfield(db_field, request, **kwargs)
if db_field.name == 'company':
field.initial = self.parent_obj.company if self.parent_obj else None
return field
get_formset() adds the parent object to each inline instance. formfield_for_dbfield() uses that parent object to populate the initial value of the company field.

Django forms don't get up-to-date from db

I have form where I print all of records from table(lets say its 'item' table in database). User can add new items to db using ajax. Data saves to db correct but when I refresh page i don't see new tags in my multi select box.
I thought cache is a problem but it doesn't.
So I have question: Where is a problem that I can see add records correct but when i refresh this same page where every time select all rows from table then i don't see these new records?
I'm using sqlite and it's development server.
Forms.py:
BLANK_CHOICE = (('', '---------'),)
class OrderCreateForm(forms.ModelForm):
tag_from = forms.MultipleChoiceField(label='Tags', choices=OrderItemList.objects.all().values_list('id', 'name'))
tag_to = forms.MultipleChoiceField()
class Meta:
model = Order
fields = ('price', 'client', 'platform')
def __init__(self, request_client_id, *args, **kwargs):
super(OrderCreateForm, self).__init__(*args, **kwargs)
self.fields['platform'].choices = BLANK_CHOICE + tuple(
Platform.objects.filter(client_id=request_client_id).values_list('id', 'name'))
View.py:
#user_passes_test(lambda u: u.is_staff, login_url='/account/login/')
def order_create(request, request_client_id):
dict = {}
dict['form_order'] = OrderCreateForm(request_client_id)
return render(request, 'panel/order/form.html', dict)
The problem is that you are setting the tag_from choices in the field definition, so the choices are evaluated once when the form first loads. You can fix the problem by setting the choices in the __init__ method instead.
class OrderCreateForm(forms.ModelForm):
tag_from = forms.MultipleChoiceField(label='Tags', choices=())
...
def __init__(self, request_client_id, *args, **kwargs):
super(OrderCreateForm, self).__init__(*args, **kwargs)
self.fields['tag_from'].choices = OrderItemList.objects.all().values_list('id', 'name'))
...
Another option would be to use a ModelMultipleChoiceField instead of a regular multiple choice field. With a model multiple choice field, Django will evaluate the queryset each time the form is initialised.
class OrderCreateForm(forms.ModelForm):
tag_from = forms.MultipleChoiceField(label='Tags', queryset=OrderItemList.objects.all())

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)

CBV Django Form View set data for ChoiceField

I'm using the Django Form View and I want to enter custom choices per user to my Choicefield.
How can I do this?
Can I use maybe the get_initial function?
Can I overwrite the field?
When I want to change certain things about a form such as the label text, adding required fields or filtering a list of choices etc. I follow a pattern where I use a ModelForm and add a few utility methods to it which contain my overriding code (this helps keep __init__ tidy). These methods are then called from __init__ to override the defaults.
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('country', 'contact_phone', )
def __init__(self, *args, **kwargs):
super(ProfileForm, self).__init__(*args, **kwargs)
self.set_querysets()
self.set_labels()
self.set_required_values()
self.set_initial_values()
def set_querysets(self):
"""Filter ChoiceFields here."""
# only show active countries in the ‘country’ choices list
self.fields["country"].queryset = Country.objects.filter(active=True)
def set_labels(self):
"""Override field labels here."""
pass
def set_required_values(self):
"""Make specific fields mandatory here."""
pass
def set_initial_values(self):
"""Set initial field values here."""
pass
If the ChoiceField is the only thing you're going to be customising, this is all you need:
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('country', 'contact_phone', )
def __init__(self, *args, **kwargs):
super(ProfileForm, self).__init__(*args, **kwargs)
# only show active countries in the ‘country’ choices list
self.fields["country"].queryset = Country.objects.filter(active=True)
You can then make your FormView use this form with like this:
class ProfileFormView(FormView):
template_name = "profile.html"
form_class = ProfileForm

Django: How to remove fields from the admin form for specific users?

My admin looks like this (with no exclude variable):
class MovieAdmin(models.ModelAdmin)
fields = ('name', 'slug', 'imdb_link', 'start', 'finish', 'added_by')
list_display = ('name', 'finish', 'added_by')
list_filter = ('finish',)
ordering = ('-finish',)
prepopulated_fields = {'slug': ('name',)}
form = MovieAdminForm
def get_form(self, request, obj=None, **kwargs):
form = super(MovieAdmin, self).get_form(request, obj, **kwargs)
form.current_user = request.user
return form
admin.site.register(Movie, MovieAdmin)
The form:
class MovieAdminForm(forms.ModelForm):
class Meta:
model = Movie
def save(self, commit=False):
instance = super(MovieAdminForm, self).save(commit=commit)
if not instance.pk and not self.current_user.is_superuser:
if not self.current_user.profile.is_manager:
instance.added_by = self.current_user.profile
instance.save()
return instance
I'm trying to remove the added_by field for users since I'd prefer to populate that from the session. I've tried methods from the following:
Django admin - remove field if editing an object
Remove fields from ModelForm
http://www.mdgart.com/2010/04/08/django-admin-how-to-hide-fields-in-a-form-for-certain-users-that-are-not-superusers/
However with each one I get: KeyError while rendering: Key 'added_by' not found in Form. It seems I need to remove the field earlier in the form rendering process but I'm stuck on where to do this.
So how can I exclude the added_by field for normal users?
You're probably getting that error when list_display is evaluated. You can't show a field that's excluded. The version with added_by removed also needs a corresponding list_display.
def get_form(self, request, obj=None, **kwargs):
current_user = request.user
if not current_user.profile.is_manager:
self.exclude = ('added_by',)
self.list_display = ('name', 'finish')
form = super(MovieAdmin, self).get_form(request, obj, **kwargs)
form.current_user = current_user
return form