I have a model where is save one of the groups of the user in my model.
Because user can be member of different groups i need to update the Selection field in my Form depending on the request.user.groups.all() queryset.
I tried to pass the initial variable to my form but this was ignored.
Any hint how to deal with this problem?
EDIT:
My view:
form = CoronaEventModelForm(request.POST or None, initial={'group': request.user.groups.all()})
my model:
user = models.ForeignKey(curr_User, default=1, null=True, on_delete=models.SET_DEFAULT, related_name='group')
group = models.ForeignKey(Group, on_delete=models.CASCADE)
my form:
class CoronaEventForm(forms.Form):
user = forms.CharField()
group = forms.CharField()
title = forms.CharField()
description = forms.CharField()
slug = forms.SlugField()
event_date = forms.DateField()
class CoronaEventModelForm(forms.ModelForm):
class Meta:
model = CoronaEvent
fields = ['group', 'title', 'description', 'slug', 'event_date']
it works with normal text fields but not with the group field.
solved it by adding a init function to my form and passing the object during instantiation of the form.
Related
I have a database of maybe 100 users that have each 5-10k products linked to them. In the admin panel loading that page is really slow because of the many products. So what I want to do is replacing it with a regex or at least a number input field that does not preload all the products:
models:
class Store(models.Model):
name = models.CharField("name", max_length = 128)
user = models.OneToOneField(User, on_delete = models.CASCADE, )
testproduct = models.Foreignkey(Product, on_delete = models.CASCADE, null = True, blank = True)
class Product(models.Model):
data = models.JSONField()
number = models.PositiveIntegerField()
store = models.ForeignKey(Store, on_delete = models.CASCADE)
admin:
class StoreAdmin(admin.ModelAdmin):
list_display = ["name", ...]
raw_id_fields = ["testproduct", ...]
This way I get an id input field on the admin page:
Is there any way I can make it a regex field, so I can search through the data attribute of my products as well as the number attribute?
I think, best way to achieve your goal is to add extra field to your model admin form and override save method for it, try this:
#admin.py
from django import forms
class StoreAdminModelForm(forms.ModelForm):
regex_field = forms.CharField()
def save(self, commit=True):
product_regex = self.cleaned_data.get('regex_field', None)
instance = super(StoreAdminModelForm, self).save(commit=commit)
# put some more regex validations here
if len(product_regex) > 0:
product = Product.objects.filter(data__regex=product_regex).first()
if product:
instance.testproduct = product
else:
raise forms.ValidationError('Product with data satisfying regex was not found!')
if commit:
instance.save()
return instance
class Meta:
model = Store
fields = '__all__'
class StoreAdmin(admin.ModelAdmin):
form = StoreAdminModelForm
raw_id_fields = ["testproduct", ]
fields = ('name ', 'user', 'testproduct', 'regex_field')
readonly_fields = ('user', )
So, the result - you will have special field for regex expression and you will try to get testproduct based on that field (without touching raw_id_field)
I have a model which I will update using an updateView generic class based function. How can I make specific fields as read only ?
example :
Models.py:
class Employee(models.Model):
emp_no = models.IntegerField( primary_key=True)
birth_date = models.DateField()
first_name = models.CharField(max_length=14)
last_name = models.CharField(max_length=16)
gender = models.CharField(max_length=1)
hire_date = models.DateField()
class Meta:
verbose_name = ('employee')
verbose_name_plural = ('employees')
# db_table = 'employees'
def __str__(self):
return "{} {}".format(self.first_name, self.last_name)
views.py :
class EmployeeUpdate(UpdateView):
model = Employee
fields = '__all__'
How can I make the first_name readonly inside a UpdateView ?
Note: I want to have the model(charfield) the same, But it should be read only inide an UpdateView.
When one wants to customize their forms the easiest way is to make a form class. A generic view is not really meant to provide all features a form class does, even though it makes life a little easy by generating the form for you by itself.
You want to be using a ModelForm [Django docs] and set disabled=True [Django docs] on your field:
from django import forms
class EmployeeUpdateForm(forms.ModelForm):
first_name = forms.CharField(max_length=14, disabled=True)
class Meta:
model = Employee
fields = '__all__'
Note: The disabled boolean argument, when set to True, disables a form field using the disabled HTML attribute so that it won’t be
editable by users. Even if a user tampers with the field’s value
submitted to the server, it will be ignored in favor of the value from
the form’s initial data.
Now in your view you simply want to set the form_class attribute:
class EmployeeUpdate(UpdateView):
model = Employee
form_class = EmployeeUpdateForm
I am working on an application where currently, I have
1) Staff Model is connected to User model via OneToOne Relationship and can have more than one Group.
2) Meeting model can also assigned to many Group.
3) RSVPinline is a part with MeetingAdmin as a inline form.
Here i was trying to automatically ADD all 'Staff' associated in Selected Groups in django admin form while creating Meetings.
I have tried save_model to add current user in meeting's 'creator' field.
models.py
class Group(models.Model):
name = models.CharField(max_length=200)
class Staff(models.Model):
fullname = models.CharField(max_length = 250,verbose_name = "First Name")
group = models.ManyToManyField(Group, blank=False,verbose_name = "Meeting Group") # protect error left to add
is_active = models.BooleanField(default=True)
user = models.OneToOneField(User, on_delete=models.CASCADE,null=True, blank=True,verbose_name = "Associated as User") # protect error left to add
left_date = models.DateField(null=True, blank=True,verbose_name = "Resigned Date")
class Meeting(models.Model):
title = models.CharField(_('Title'), blank=True, max_length=200)
start = models.DateTimeField(_('Start'))
group = models.ManyToManyField(Group, blank=False,verbose_name = "Meeting Group") # protect error left to add
location = models.ForeignKey(Location, blank=False,verbose_name = "Location",on_delete=models.CASCADE) # protect error left to add
class RSVP(models.Model):
meeting = models.ForeignKey(Meeting, on_delete=models.CASCADE)
responder = models.ForeignKey(User, editable=True, on_delete=models.CASCADE, null=True, blank=True,verbose_name = "Attendees", related_name='guest')
response = models.CharField(max_length = 20, choices= MEETING_RSVP, default='No response', verbose_name = "Status",null=True, blank=True)
admin.py
class RSVPInline(admin.TabularInline):
model = RSVP
extra = 0
class MeetingAdmin(admin.ModelAdmin):
form = MeetingForm
list_display = ('title', 'location', 'start','creator' )
inlines = [RSVPInline, TaskInline]
#Currently using save_model to automatically add current user as a creator
def save_model(self, request, obj, form, change):
obj.creator = request.user
super().save_model(request, obj, form, change)
My pseudo code is:
grouplist = Get group's list from submitted MeetingForm
stafflist = Staff.objects.filter(department__in =grouplist).values_list('id', flat=True).distinct()
Add to RSVPInline:
values = list(for staff in stafflist:
'responder' = staff
'meeting' = 'meeting from MeetingForm'
'response' = has a default value in model
bulk_create() RSVPInline with values
You can extend save_related() ModelAdmin method to perform additional actions after form object (Meeting) and its Inlines (RSVPs, if present in submitted form) are saved:
class MeetingAdmin(admin.ModelAdmin):
...
def save_related(self, request, form, formsets, change):
# call original method - saves Meeting and inlines
super(MeetingAdmin, self).save_related(request, form, formsets, change)
# get this form Meeting
obj = form.instance
# get Staff members of this meeting groups
# and we can exclude ones already having
# RSVP for this meeting
stafflist = Staff.objects.filter(
group__in=obj.group.all()
).exclude(
user__guest__meeting=obj
)
rsvps = list(
RSVP(responder=staff.user, meeting=obj)
for staff in stafflist
)
# calls bulk_create() under the hood
obj.rsvp_set.add(*rsvps, bulk=False)
** Few possibly useful notes:
group field may be better to be called groups as it represents ManyToMany relation and returns multiple objects
related_name represents relation from the related object back to this one so it may be more logical to use something like invites instead of guest
I'm using Django 2.x and Django REST Framework.
I have two models like
class Contact(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, on_delete=models.PROTECT)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100, blank=True, null=True)
modified = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
class AmountGiven(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
contact = models.ForeignKey(Contact, on_delete=models.PROTECT)
amount = models.FloatField(help_text='Amount given to the contact')
given_date = models.DateField(default=timezone.now)
created = models.DateTimeField(auto_now=True)
the serializer.py the file has serializers defined as
class ContactSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Contact
fields = ('id', 'first_name', 'last_name', 'created', 'modified')
class AmountGivenSerializer(serializers.ModelSerializer):
contact = ContactSerializer()
class Meta:
model = AmountGiven
depth = 1
fields = (
'id', 'contact', 'amount', 'given_date', 'created'
)
views.py
class AmountGivenViewSet(viewsets.ModelViewSet):
serializer_class = AmountGivenSerializer
def perform_create(self, serializer):
save_data = {}
contact_pk = self.request.data.get('contact', None)
if not contact_pk:
raise ValidationError({'contact': ['Contact is required']})
contact = Contact.objects.filter(
user=self.request.user,
pk=contact_pk
).first()
if not contact:
raise ValidationError({'contact': ['Contact does not exists']})
save_data['contact'] = contact
serializer.save(**save_data)
But when I add a new record to AmountGiven model and passing contact id in contact field
it is giving error as
{"contact":{"non_field_errors":["Invalid data. Expected a dictionary, but got str."]}}
When I remove contact = ContactSerializer() from AmountGivenSerializer, it works fine as expected but then in response as depth is set to 1, the contact data contains only model fields and not other property fields defined.
I'm not a big fan of this request parsing pattern. From what I understand, you want to be able to see all the contact's details when you retrieve an AmountGiven object and at the same time be able to create and update AmountGiven by just providing the contact id.
So you can change your AmountGiven serializer to have 2 fields for the contact model field. Like this:
class AmountGivenSerializer(serializers.ModelSerializer):
contact_detail = ContactSerializer(source='contact', read_only=True)
class Meta:
model = AmountGiven
depth = 1
fields = (
'id', 'contact', 'contact_detail', 'amount', 'given_date', 'created'
)
Note that the contact_detail field has a source attribute.
Now the default functionality for create and update should work out of the box (validation and everything).
And when you retrieve an AmountGiven object, you should get all the details for the contact in the contact_detail field.
Update
I missed that you need to check whether the Contact belongs to a user (however, I don't see a user field on your Contact model, maybe you missed posting it). You can simplify that check:
class AmountGivenViewSet(viewsets.ModelViewSet):
serializer_class = AmountGivenSerializer
def perform_create(self, serializer):
contact = serializer.validated_data.get('contact')
if contact.user != self.request.user:
raise ValidationError({'contact': ['Not a valid contact']})
serializer.save()
Override the __init__() method of AmountGivenSerializer as
class AmountGivenSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
super(AmountGivenSerializer, self).__init__(*args, **kwargs)
if 'view' in self.context and self.context['view'].action != 'create':
self.fields.update({"contact": ContactSerializer()})
class Meta:
model = AmountGiven
depth = 1
fields = (
'id', 'contact', 'amount', 'given_date', 'created'
)
Description
The issue was the DRF expects a dict like object from contact field since you are defined a nested serializer. So, I removed the nested relationship dynamically with the help of overriding the __init__() method
For those who got here but have relatively simple serializers, this error can also occur when the request data is malformed, in my case JSON encoded twice.
The serializer will decode the JSON, but as it is encoded twice request.data will still be a string. The error therefore makes sense as a "dictionnary" was expected, but we still have a "string".
You can check the output of the following to confirm whether this is the issue you are experiencing:
print(type(request.data)) # Should be <class 'dict'>
I've been trying to solve this problem for a couple of days now, getting quite desperate. See the commented out code snippets for some of the things I've tried but didn't work.
Problem: How can I limit the values in the category field of the IngredientForm to only those belonging to the currently logged in user?
views.py
#login_required
def apphome(request):
IngrFormSet = modelformset_factory(Ingredient, extra=1, fields=('name', 'category'))
# Attempt #1 (not working; error: 'IngredientFormFormSet' object has no attribute 'fields')
# ingrformset = IngrFormSet(prefix='ingr', queryset=Ingredient.objects.none())
# ingrformset.fields['category'].queryset = Category.objects.filter(user=request.user)
# Attempt #2 (doesn't work)
# ingrformset = IngrFormSet(prefix='ingr', queryset=Ingredient.objects.filter(category__user_id = request.user.id))
models.py:
class Category(models.Model):
name = models.CharField(max_length=30, unique=True)
user = models.ForeignKey(User, null=True, blank=True)
class Ingredient(models.Model):
name = models.CharField(max_length=30, unique=True)
user = models.ForeignKey(User, null=True, blank=True)
category = models.ForeignKey(Category, null=True, blank=True)
counter = models.IntegerField(default=0)
forms.py:
class IngredientForm(ModelForm):
class Meta:
model = Ingredient
fields = ('name', 'category')
UPDATE: I've made some progress but the solution is currently hard-coded and not really usable:
I found out I can control the categoryform field via form class and then pass the form in the view like this:
#forms.py
class IngredientForm(ModelForm):
category = forms.ModelChoiceField(queryset = Category.objects.filter(user_id = 1))
class Meta:
model = Ingredient
fields = ('name', 'category')
#views.py
IngrFormSet = modelformset_factory(Ingredient, form = IngredientForm, extra=1, fields=('name', 'category'))
The above produces the result I need but obviously the user is hardcoded. I need it to be dynamic (i.e. current user). I tried some solutions for accessing the request.user in forms.py but those didn't work.
Any ideas how to move forward?
You don't need any kind of custom forms. You can change the queryset of category field as:
IngrFormSet = modelformset_factory(Ingredient, extra=1, fields=('name', 'category'))
IngrFormSet.form.base_fields['category'].queryset = Category.objects.filter(user__id=request.user.id)
Category.objects.filter(user=request.user)
returns a list object for the initial value in your form which makes little sense.
Try instead
Category.objects.get(user=request.user)
or
Category.objects.filter(user=request.user)[0]