Using fields from models in django-form-utils fieldsets - django

Sorry if I have overlooked something in docs of django-forms-utils by Carl Meyer. We're using it for the getting fieldset in non-admin pages in django. Here is my code in forms.py:
class MentorApplicationForm(ApplicationForm):
email = forms.EmailField(label='Email', error_messages={'email_exists': 'This email id has already been used. Please use a different email id to submit a new application. Or write to applications#mentortogether.org for clarifications.'},)
dob = forms.DateField(label=u'Date of Birth',
help_text=u'Format dd/mm/yyyy',
required=True,
input_formats=("%d/%m/%Y",))
class Meta:
model = MentorApplication
fieldsets = [('Personal Information', {'fields': ['email','dob'],})]
What I want is something like this:
class MentorApplicationForm(ApplicationForm):
email = forms.EmailField(label='Email', error_messages={'email_exists': 'This email id has already been used. Please use a different email id to submit a new application. Or write to applications#mentortogether.org for clarifications.'},)
dob = forms.DateField(label=u'Date of Birth',
help_text=u'Format dd/mm/yyyy',
required=True,
input_formats=("%d/%m/%Y",))
class Meta:
model = MentorApplication
fieldsets = [('Personal Information', {'fields': ['first_name', 'last_name', 'email','dob',],})]
where I have defined 'first_name' and 'last_name' in the MentorApplication models and I don't want to redefine them in forms.py. Here is the doc where it says that it extends from ModelForm and that's where I expected that it would take the fields from the MentorApplication model(mentioned in Meta). Can some one help me out finding the reason and also suggest me a workaround(I am quite resistant on redefining it in form, since I'll have to repeat myself). Thanks in advance.

I was extending ApplicationForm (super of MentorApplicationForm) from the BetterForm. I should have instead extended it from the 'BetterModelForm'.

Related

How can I serialize a Many to Many field with added data in Django Rest Framework, without extra keys in the response?

I am using Django 3.2 and Django Rest Framework 3.14.
I have Users that should be able to follow other Users, like on a social networking site. When serializing a full User, I would like to get a list of those following Users, as well as additional data, such as the follow datetime. However, I would like the response to stay "flat", like this:
{
"username":"admin",
"followers":[
{
"username":"testuser",
--additional user fields--
"follow_date":"2023-02-08 01:00:02"
},
--additional followers--
],
--additional user fields--
}
I can only seem to go "through" my join model using an extra serializer, and end up with this:
{
"username":"admin",
"followers":[
{
"user":{
"username":"testuser",
--additional user fields--
},
"follow_date":"2023-02-08 01:00:02"
},
--additional followers--
],
--additional user fields--
}
Note how there is an additional user key
"user":{
"username":"testuser",
--additional user fields--
},
I don't want that there!
My model looks something like this:
class Follower(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.RESTRICT, related_name="followers")
follower = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.RESTRICT, related_name="following")
follow_date = models.DateTimeField(auto_now_add=True)
My serializers look like this (extra fields trimmed for brevity):
class UserSnippetSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username']
class FollowerSerializer(serializers.ModelSerializer):
user = UserSnippetSerializer(source='follower')
class Meta:
model = Follower
fields = ['user', 'follow_date']
class FullUserSerializer(serializers.ModelSerializer):
followers = FollowerSerializer(source='user.followers', many=True)
following = FollowerSerializer(source='user.following', many=True)
class Meta:
model = Profile
fields = ['username', 'followers', 'following']
Given this, I understand why it's doing what it is. But I can't seem to find any way to skip the extra user key, while still including bot the User information AND the follow_date.
I've tried playing around with proxy models on User, but that didn't seem to help. I suspect that this is no configuration thing, though I would like it to be, and that I need to override some internal function. But I can't seem to locate what that would be.
What's the best (and hopefully easiest!) way to accomplish this?
One approach is to use a serializer method field on FollowerSerializer like this:
class FollowerSerializer(serializers.ModelSerializer):
username = serializers.SerializerMethodField()
class Meta:
model = Follower
fields = ['username', 'follow_date']
def get_username(self, obj):
return obj.follower.username
or if you have lots of fields to display from user, you can also override to_representation like this:
class FollowerSerializer(serializers.ModelSerializer):
user = UserSnippetSerializer(source='follower')
class Meta:
model = Follower
fields = ['user', 'follow_date']
def to_representation(self, instance):
data = super().to_representation(instance)
user_data = data.pop("user")
# flatten the follower data with the user data
return {
**data,
**user_data,
}
Note that this will also affect FullUserSerializer.following

Password fields rendering as plain text in documentation

I am using Django rest framework 3.1 and django-rest-swagger 0.3.2. Things are working fairly well however I am having an issue with the password input fields in my login serializer. My login serializer is pretty simple, it inherits from rest_framework.authtoken.serializers.AuthTokenSerializer:
class MyLoginSerializer(AuthTokenSerializer):
def validate(self, attrs):
# small amount of validation logic here
The AuthTokenSerializer has the password field defined with the proper style:
class AuthTokenSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField(style={'input_type': 'password'})
But Swagger displays the password input in the POST documentation as plain text (input type='text'). I'm not sure what is going wrong here, shouldn't django-rest-swagger be able to interpret the style here and render the input accordingly? Am I missing something? Any advice is appreciated, thanks much!
I'm still using DRF 2.4.4, but this is what I have to * out the field:
from django.forms.widgets import PasswordInput
class UserSerializer(serializers.HyperlinkedModelSerializer):
password = serializers.CharField(
required=False,
write_only=True, # <- never send password (or hash) to the client
allow_none=True, # <- allow users to not specify a password
blank=True,
# this creates a password input field for DRF-only
# front end (not sure about swagger)
# vvvvvvvvvvvvvvvvvv
widget=PasswordInput
)
class Meta:
model = User
fields = ('url', 'id', 'username', 'password', [...]

Django - clean_field() is not being called for updates on admin inline form

I can not figure out why a clean_field() method is not being called for an inline form that is updated on an admin view. The code I have seems straight-forward (see synopsis below).
When I modify the Primary form through admin interface (http://admin/..../primary/1/), as expected, I see:
Admin.PrimaryAdminForm.clean_myfield() called
Admin.PrimaryAdminForm.clean() called
Model.Primary.clean() called
However, when I modify the Primary as seen as an inline on the Admin view of Membership (http://admin/..../membership/1/), I only see:
Model.Primary.clean() called
I have tried placing the "def clean_myfield(self):" method in the following locations but can not see it get executed from the Membership inlined Primary form:
Model.Primary.clean_myfield
Admin.PrimaryAdmin.clean_myfield
Admin.PrimaryAdminForm.clean_myfield
Admin.PrimaryAdminInline.clean_myfield
Is there somewhere else this clean_myfield code should be placed?
I have read (and reread) the Django docs on [forms and field validation][docs.djangoproject.com/en/dev/ref/forms/validation/#form-and-field-validation] which gives a great coverage, but there's nothing on inline validation. I've also read docs.djangoproject.com/en/dev/ref/contrib/admin/#adding-custom-validation-to-the-admin, but no help for inline specific validation. Is there other documentation on this?
---> Answered by Austin provided a doc reference to: "If not specified" (see his link) , which implies the answer. I added a request to improve the documents on this topic.
After further experimenting I found a workaround by putting code in the Model.Primary.clean() method:
def clean(self):
data = self.myfield
data += "_extra" # not actual cleaning code
self.myfield = data
So the question remains: Why is Model.clean() seem to be the only place to put admin inline form validation and not in a clean_myfield(self) method?
---> Answered by Austin. I needed add form = PrimaryAdminForm to PrimaryInline. With this addition, PrimaryAdminForm.clean_myfield(self) is called when PrimaryInline myfield is updated on Membership form. Code ordering was updated due to the added form reference.
Code synopsis:
No forms.py file -- all models are updated through admin interface
models.py:
class Membership(models.Model):
name = models.CharField( max_length=NAME_LENGTH,
null=True, blank=True, verbose_name="Membership Name Tag",
help_text="Name of membership" )
class Primary(models.Model):
user = models.OneToOneField(User, verbose_name="User Name")
membership = models.OneToOneField(Membership, verbose_name="Membership Name")
myfield = models.CharField("My Field", max_length=20, null=True, blank=True)
# clean method altered as in Update comment
# Why must this be here? Why not in clean_myfield(self)
def clean(self):
data = self.myfield
data += "_extra" # not actual cleaning code
self.myfield = data
admin.py:
class MembershipAdminForm(ModelForm):
class Meta:
model = Membership
class PrimaryAdminForm(ModelForm):
class Meta:
model = Primary
def clean_myfield(self):
data = self.cleaned_data['myfield']
data += "_extra" # not actual cleaning code
return unicode(data)
def clean(self):
cleaned_data = super(PrimaryAdminForm, self).clean()
# not actual cleaning code
return cleaned_data
# EDIT2: Moved PrimaryInline so it's defined after PrimaryAdminForm
class PrimaryInline(admin.StackedInline):
model = Primary
form = PrimaryAdminForm #EDIT2 as recommended by Austin
raw_id_fields = ['user']
verbose_name_plural = 'Primary Member'
fieldsets = ((None, {'classes': ('mbship', ),
'fields': ('user', 'myfield')}), )
class MembershipAdmin(admin.ModelAdmin):
form = MembershipAdminForm
# inlines
inlines = [PrimaryInline, ]
fieldsets = ((None, {'classes': ('mbship',),
'fields': ('name'), }), )
class PrimaryAdmin(admin.ModelAdmin):
form = PrimaryAdminForm
list_display = ('__unicode__', 'user', 'status', 'date_used' )
search_fields = ['user__first_name', 'user__last_name', 'user__email']
fieldsets = ((None, {'classes': ('mbship',),
'fields': ('user', 'membership', 'myfield'), }), )
def clean_myfield(self):
data = self.cleaned_data['myfield']
data += "_extra" # not actual cleaning code
return unicode(data)
Validation occurs on ModelForm objects, not ModelAdmin objects. If you want to override any clean methods then you have to create your own ModelForm descendent for each required model.
In your example, the PrimaryInline class does not specify a form. If not specified, the form used will be a ModelForm, which doesn't have any of your custom clean methods.
Try this:
class PrimaryInline(admin.StackedInline):
# ... existing code ...
form = PrimaryAdminForm
This will now use your custom PrimaryAdminForm with associated clean() methods.

Can't display DateField on form with auto_now = True

I have a model with auto_now, and auto_now_add set for Update and Create fields:
class HotelProfiles(models.Model):
fe_result_id = models.AutoField(primary_key=True)
fe_created_date = models.DateTimeField(verbose_name='Created',
blank=True,
auto_now_add=True)
fe_updated_date = models.DateTimeField(verbose_name='Updated',
blank=True,
auto_now=True)
In the Admin it displays both fields but leaves them uneditable. They
don't seem to be passed to my form to be rendered. I don't want them
to be editable, but I would like to display at the top of my form.
How can I do this?
This is in my HotelProfilesAdmin class:
readonly_fields = ('fe_result_id', 'fe_created_date', 'fe_updated_date', 'fe_owner_uid')
#date_hierarchy = 'lto_end_date'
fieldsets = (
("Internal Use Only", {
'classes': ('collapse',),
'fields': ('fe_result_id', 'fe_created_date', 'fe_owner_uid', 'fe_updated_date', 'fe_result_status')
}),
Make the fields you want readonly
explicitly override what fields are available in this admin form (readonly fields will be present but readonly)
Example:
from django.contrib import admin
class HotelProfilesAdmin(admin.ModelAdmin) :
# Keep the fields readonly
readonly_fields = ['fe_created_date','fe_updated_date']
# The fields in the order you want them
fieldsets = (
(None, {
'fields': ('fe_created_date', 'fe_updated_date', ...other fields)
}),
)
# Add your new adminform to the site
admin.site.register(HotelProfiles, HotelProfilesAdmin)
For the benefit of others, I figured out a way to do this. I'm new to Django, so if there is a better way, I'd be interested in hearing it. The view code is below. I wasn't sure if Django was not returning the fields from the query, and I found out that it was. So, something in the renderering of the form that I don't understand removed those fields so they couldn't be rendered. So, I copied them to a dict called read_only before rendering and passed it along.
try:
hotel_profile = HotelProfiles.objects.get(pk=hotel_id)
read_only["created_on"] = hotel_profile.fe_created_date
read_only["updated_on"] = hotel_profile.fe_updated_date
f = HotelProfileForm(instance=hotel_profile)
#f.save()
except:
f = HotelProfileForm()
print 'rendering blank form'
return render_to_response('hotels/hotelprofile_form.html', {'f' : f, 'read_only': read_only}, context_instance=RequestContext(request))

Django-piston and UserProfile

I'm using Django-piston and I'd like to get user objects that include user profile data.
I'm trying :
class UserHandler(BaseHandler):
model = User
fields = ('id', 'username', 'favorite_color')
...
favorite_color is defined in UserProfile
The result is only printing id and username and nothing for favorite color.
If your UserProfile is linked to the User via a OneToOneField, you should be able to do it by walking the relation, using the nested-tuple syntax. (The following is untested)
class UserHandler(BaseHandler):
model = User
fields = ('id', 'username', ('userprofile', ('favorite_color',))
...
See the docs here
check if you already use model=User in other handler
and look at this
https://bitbucket.org/jespern/django-piston/wiki/FAQ