Django - Custom admin save model - python

I have two models: Page and a custom user model MyUser
These two models have each one a manytomanyfield:
class Page(models.Model):
members = models.ManyToManyField(settings.AUTH_USER_MODEL)
class MyUser(AbstractUser):
mypages = models.ManyToManyField(Page)
objects = UserManager()
When I add a member to the members manytomanyfield via the admin interface, I would like to add the new attributed Page object to the user automatically in the mypages attribute, so I am trying to override the save method of the Page model in the admin.py:
class PageAdmin(admin.ModelAdmin):
def save_related(self, request, form, formsets, change):
super(ModelAdmin, self).save_related(request, form, formsets, change)
if 'members' in form.changed_data:
#And I am quite lost....
Is it a good idea? Should I do that in the "Page model custom save" method in my models.py file either doing it in the admin.py?
I tried to replace the #And I am quite lost.... part with ideas from:
Link1
Link2
Without any sucess!

This is completely mistaken. A many-to-many field is already double-ended. You don't need to define it on both ends. When you define a members field on Page, then MyUser will automatically get a page_set accessor which is the other end of the relation, and any page that adds a user to its members will automatically show up in the user's page_set. There is no need for any code.

Related

Django: How to fill inherited form fields from an instance of the base model class?

I have a model Customer, which inherits from a User model, and adds a new field.
class Customer(User):
customer_since: models.Datefield()
I then want to use use a generic view to create customers:
class CustomerCreate(CreateView):
model = Customer
form_class = CustomerForm
When I create a new customer, I want to use the data from request.user to autofill the form with the inherited fields. How can I do that?
I know that if I was creating the record manually, I would do it like this:
Customer.objects.create(user_ptr=request.user, customer_since=datetime.date.today())
but with a generic view, the form is generated automatically and saved by the form_valid method of the CreateView, how do I tell the form to include the proper user reference?

Rendering PK instead of username in CreateView

I am building an app where managers can create a private webpage, they need to add people manually in order for them to access the page.
I don't want the managers to see all of the users, So I would like to render only the PK in the list.
My views.py
class HotelCreateView(LoginRequiredMixin, CreateView):
model = Hotel
form_class = HotelForm
def form_valid(self, form):
form.instance.manager_hotel = self.request.user
return super().form_valid(form)
forms.py
from django.db import models
from django.forms import ModelForm
from .models import Hotel
class ColleagueChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.get_pk()
class HotelForm(models.Model):
ColleagueModelChoiceField(queryset=Colleague.objects.filter(pk))
You're setting fields on your CreateView, so you're letting Django generate the ModelForm automatically for you. The form uses a ModelMultipleChoiceField, which derives from ModelChoiceField, described here.
If you read the last paragraph of that section, you'll see that the display values for such a field are coming from the model's __str__ method, or you can override this with the label_from_instance() method.
That's therefore what you need to do, override this method on the ModelMultipleChoiceField. But to do that, you need to specify your own form.
So:
Create your own ModelForm for your Hotel model (HotelForm).
Create a subclass of ModelMultipleChoiceField (ColleagueChoiceField) and override the label_from_instance() method to display the pk.
Set the colleagues field on the HotelForm to be a ColleagueChoiceField.
Remove the fields attribute on your view and set the form_class to your HotelForm instead.

Indirect inline in Django admin

I have the following models:
class UserProfile(models.Model):
user = models.OneToOneField(User)
class Property(models.Model):
user = models.ForeignKey(User)
I would like to create a TabularInline displaying every Property connected to a particular UserProfile on its Django admin page. The problem here is, of course, that Property does not have a ForeignKey directly to UserProfile, so I cannot simply write
class PropertyTabularInline(admin.TabularInline):
model = Property
class UserProfileAdmin(admin.ModelAdmin):
inlines = (PropertyTabularInline,)
How can I easily do what I want?
You can overwrite the User admin page to display both the Profile and the Property models.
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from myapp.models import *
class ProfileInline(admin.TabularInline):
model = Profile
class PropertyInline(admin.TabularInline):
model = Property
class UserAdmin(UserAdmin):
inlines = (ProfileInline, PropertyInline,)
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
You can also remove any unwanted/unused User properties from being displayed (e.g. Groups or Permissions)
more here: https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#extending-the-existing-user-model
and here:
https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#a-full-example
class PropertyTabularInline(admin.TabularInline):
model = Property
def formfield_for_dbfield(self, field, **kwargs):
if field.name == 'user':
# implement your method to get userprofile object from request here.
user_profile = self.get_object(kwargs['request'], UserProfile)
kwargs["queryset"] = Property.objects.filter(user=user_profile)
return super(PropertyInLine, self).formfield_for_dbfield(field, **kwargs)
once this is done, you can add this inline to user UserProfileAdmin like:
class UserProfileAdmin(admin.ModelAdmin):
inlines = (PropertyTabularInline,)
Haven't tested it, but that should work.
It is achievable by making one change in your models.
Instead of creating OneToOne relationship from UserProfile to User, subclass User creating UserProfile. Code should look like that:
class UserProfile(User):
# some other fields, no relation to User model
class Property(models.Model):
user = models.ForeignKey(User)
That will result in creating UserProfile model that have hidden OneToOne relation to User model, it won't duplicate user model.
After doing that change, your code will work. There are some changes under the hood, like UserProfile no longer have it's own ID, you can access fields from User inside UserProfile and it's hard to swap User model using settings.AUTH_USER_MODEL (that will require creating some custom function returning proper type and changing migration by hand) but if this is not a problem for you, it may be good solution.

Django: Faking a field in the admin interface?

I have a model, Foo. It has several database properties, and several properties that are calculated based on a combination of factors. I would like to present these calculated properties to the user as if they were database properties. (The backing factors would be changed to reflect user input.) Is there a way to do this with the Django admin interface?
I would suggest you subclass a modelform for Foo (FooAdminForm) to add your own fields not backed by the database. Your custom validation can reside in the clean_* methods of ModelForm.
Inside the save_model method of FooAdmin you get the request, an instance of Foo and the form data, so you could do all processing of the data before/after saving the instance.
Here is an example for a model with a custom form registered with django admin:
from django import forms
from django.db import models
from django.contrib import admin
class Foo(models.Model):
name = models.CharField(max_length=30)
class FooAdminForm(forms.ModelForm):
# custom field not backed by database
calculated = forms.IntegerField()
class Meta:
model = Foo
class FooAdmin(admin.ModelAdmin):
# use the custom form instead of a generic modelform
form = FooAdminForm
# your own processing
def save_model(self, request, obj, form, change):
# for example:
obj.name = 'Foo #%d' % form.cleaned_data['calculated']
obj.save()
admin.site.register(Foo, FooAdmin)
Providing initial values for custom fields based on instance data
(I'm not sure if this is the best solution, but it should work.)
When a modelform for a existing model instance in the database is constructed, it gets passed this instance. So in FooAdminForm's __init__ one can change the fields attributes based on instance data.
def __init__(self, *args, **kwargs):
super(FooAdminForm, self).__init__(*args, **kwargs)
# only change attributes if an instance is passed
instance = kwargs.get('instance')
if instance:
self.fields['calculated'].initial = (instance.bar == 42)
It's easy enough to get arbitrary data to show up in change list or make a field show up in the form: list_display arbitrarily takes either actual model properties, or methods defined on the model or the modeladmin, and you can subclass forms.ModelForm to add any field type you'd like to the change form.
What's far more difficult/impossible is combining the two, i.e. having an arbitrary piece of data on the change list that you can edit in-place by specifying list_editable. Django seems to only accept a true model property that corresponds to a database field. (even using #property on the method in the model definition is not enough).
Has anyone found a way to edit a field not actually present on the model right from the change list page?
In the edit form, put the property name into readonly_fields (1.2 upwards only).
In the changelist, put it into list_display.
You can use the #property decorator in your model (Python >= 2.4):
class Product(models.Model):
#property
def ranking(self):
return 1
"ranking" can then be used in list_display:
class ProductAdmin(admin.ModelAdmin):
list_display = ('ranking', 'asin', 'title')

How to upload photo into UserProfile for specified user in django?

I have a photo field in my UserProfile model, so any user is able to upload his photo. I was thinking that all I need is ModelForm for the UserProfile, but without User specified it will fail, of course. But if I pass the request.user as a parameter into a form constructor - it won't work, too, because of the thing that form's UserProfile isn't connected with this user.
class ProfileUploadPhotoForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('photo',)
Is there any way to set, which user's profile to modify?
You need to pass it an instance of a UserProfile to update.
uploadform = ProfileUploadPhotoForm(request.POST, request.FILES,\
instance=request.user.userprofile)
if uploadform.is_valid():
uploadform.save()
Problem solved!
I forgot to add
enctype="multipart/form-data"
in form attributes.

Categories

Resources