I have a Product model and a HeadFlowDataset model. These two models are connected with a foreinkey relationship, in which one product can have many headflowdataset pairs.
Their model codes are as below:
class Product(models.Model):
id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
editable=False,
)
...
# other fields in the model, not exactly related to this problem
class HeadFlowDataSet(models.Model):
id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
editable=False,
)
product = models.ForeignKey(
Product,
on_delete=models.CASCADE,
)
head = models.FloatField()
flow = models.FloatField()
def __str__(self):
return self.product.pump_name
Every product can have up to 10-15 head-flow sets. And currently I'm using Django admin panel to populate my product database. On the other hand, there may be up to 1000 products in total. So when the admin was trying to add head-flow datasets, the process was a bit challenging to find the product name from the select menu that Django admin provided. So I used Django autocomplete_fields in the ModelAdmin as below so that the admin can at least search for the product name:
class HeadFlowDatasetAdmin(admin.ModelAdmin):
list_display = (
"product",
"head",
"flow",
)
fields = (
"product",
"head",
"flow",
)
autocomplete_fields = ["product"]
def get_ordering(self, request):
return [Lower("product__pump_name"), "head"]
But this approach too is a bit frustrating to search for the product name every time we are trying to add a new head-flow dataset.
QUESTION:
How can I set a default value for this select2 field that dynamically sets the last product searched as the default value? So that when the admin tries to add a new head-flow dataset, he sees the last product selected by default until he manually changes the product?
You can use a concept in Django called InlineModelAdmin [django-docs] and in your case, your admin.py module can be as below:
from django.contrib import admin
from your_app_name.models import Product, HeadFlowDataSet
class HeadFlowDatasetInline(admin.TabularInline):
model = HeadFlowDataset
extra = 1
class ProductAdmin(admin.ModelAdmin):
list_display = (
"pump_name",
"main_model",
"usage",
"pump_type",
)
search_fields = (
"pump_name",
"main_model__model_name_fa",
"usage__usage_name_fa",
"pump_type__type_name",
)
fields = (
"main_model",
"usage",
"sub_usage",
"pump_type",
"pump_name",
"pump_image",
"NPSH_diagram_image",
"power_diagram_image",
"max_head",
"max_flow",
"motor_ph",
"motor_rpm",
"pump_technical_datasheet_fa",
"pump_technical_datasheet_en",
)
inlines = [HeadFlowDatasetInline]
def save_model(self, request, obj, form, change):
obj.created_by = request.user
obj.last_updated_by = request.user
obj.save()
def save_formset(self, request, form, formset, change):
instances = formset.save(commit=False)
for instance in instances:
instance.user = request.user
instance.save()
With the above code snippet, you have an inline part with one set of HeadFlowDatasetobjects on each page of your Product models in your Django admin panel which can be added on need form that panel and with this approach, this issue will be resolved.
Related
admin.py
class authUserMenu(admin.ModelAdmin):
list_display = ["__str__", "user_id", "menu_id","is_active"]
class Meta:
Model = AuthUserMenu
admin.site.register(AuthUserMenu, authUserMenu)
models.py
class AuthUserMenu(models.Model): # USER VS MENU relation
user = models.ForeignKey(AuthUser,on_delete=models.DO_NOTHING,blank=True, null=True)
menu = models.ForeignKey(Menu,on_delete=models.DO_NOTHING,blank=True, null=True)
is_active = models.BooleanField(default=False)
class Meta:
db_table = 'auth_user_menu'
ordering = ['user','menu','is_active']
def __str__(self):
# return self.id
return str([self.user.username,'>>>>>>', self.menu.menu_name])
In my Django admin panel
When filtering with username should only show some menus under some condition... How can I achieve this?
Suppose here for the username 4 menu is showing. But it should show 2 menu. This may obtain by db querying.
This is how A user related to menus
You need some JS to call a view asyncronously every time the user field is updated and change the options in the menu field with the response. The package django-autocomplete-light is a popular choice for exactly this, especially updating field values from other fields is relevant
forms.py
class AuthUserMenuForm(forms.ModelForm):
class Meta:
model = AuthUserMenu
fields = '__all__'
widgets = {
'menu': autocomplete.ModelSelect2(
url='menu-autocomplete',
forward=['user']
)
}
views.py
class MenuAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
qs = Menu.objects.all()
user = self.forwarded.get('user', None)
if user:
# Not clear exactly what your filter would be here so left it but you have user available
qs = qs.filter(...)
return qs
urls.py
urlpatterns = [
...
path('menu-autocomplete/', views.MenuAutocomplete.as_view(), name='menu-autocomplete'),
...
]
class AuthUserMenuAdmin(admin.ModelAdmin):
form = AuthUserMenuForm
list_display = ["__str__", "user_id", "menu_id", "is_active"]
admin.site.register(AuthUserMenu, AuthUserMenuAdmin)
Sorry But the filter's you are applying for admin is only valid for how they are Going to look on the admin panel it won't customize your model properties and in order to achieve that you need to create a filter as eg ...
user = self.get_object()
articles = Article.objects.filter(author_id)
Something like that
I have four tables connected together:
Meeting (two foreign keys of Person)
Person (many to many field to 'ConditionDictionary'
Condition (foreign key of Person and ConditionDictionary)
ConditionDictionary
my Meeting table has foreign key of Person and Person is connected many-to-many to ConditionDictionary, which goes through table Condition (because I had to add extra flags there).
I want to add one column from ConditionDictionary to meeting view, but I have no idea how can I do it.
When running in python shell, I achieve what I need
Meeting.objects.filter(id=1).values('person_in_need__person_c__condition__issue')
But, as mentioned above, I have no idea how to add the option to choose what it returns on the meeting class.
Here are the models (the important bits):
class ConditionDictionary(models.Model):
issue = models.CharField(max_length=50)
description = models.TextField()
class Condition(models.Model):
person = models.ForeignKey(Person, related_name = 'person_c', on_delete=models.CASCADE)
condition = models.ForeignKey(ConditionDictionary, related_name = 'condition_c', on_delete=models.CASCADE)
class Person(models.Model):
issue = models.ManyToManyField(ConditionDictionary, through='Condition')
class Meeting(models.Model):
person1 = models.ForeignKey(Person, related_name='in_need', on_delete=models.CASCADE)
person2 = models.ForeignKey(Person, related_name='carer', on_delete=models.CASCADE)
My idea is that with every meeting created, you could choose what issue is treated this time.
You can show related table data using django's __ (double underscore).
For example, I've got a Profile model which links to User. User also links to a Passport model.
You can see here in the search_fields that it's going to do a search on the ID of the User and also through the User table to the Passport table to search that by ID;
#admin.register(Profile)
class ProfileAdmin(admin.ModelAdmin):
""" Model admin for Profile. """
# List attributes.
list_display = (
'id',
'user',
)
list_filter = (
'series_entrant',
)
search_fields = (
'user__id',
'user__email',
'user__first_name',
'user__last_name',
'user__passport__id'
)
To display related data in the fields of an admin class you can make a callable and add it to the readonly fields. For example;
#admin.register(AddOn)
class AddOnAdmin(admin.ModelAdmin):
"""
AddOn model admin
"""
list_display = (
'id',
'get_related',
'enabled',
'created',
)
readonly_fields = (
'created',
'modified',
'get_related',
)
fields = (
'enabled',
'get_related',
'name',
'description',
'created',
'modified',
)
def get_related(self, obj):
"""
Get some related data
"""
return obj.realted_model.field
get_related.short_description = _('Related')
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 on my second Django project. On my first project I used all generic views, with only the most basic forms tied directly to a custom user model using UpdateView.
In this project I'm trying to implement user profile functionality. My custom user model has some extra dummy fields just so I can manipulate the data. This I refer so as the CustomUser model. I also have a UserAddress model containing addresses since a user can have more than one address. I have tried looking for other questions, and I get similar questions, but there is always something missing:
Django class based views - UpdateView with two model forms - one submit
Using Multiple ModelForms with Class-Based Views
Multiple Models in a single django ModelForm?
Django: multiple models in one template using forms
I've spent the last day or two looking for the "Django way" of doing what I want to do and have had no luck. My initial thought was that I could use a single template, fed with two ModelForms wrapped in a single <form> tag. The view would then process each form and update the CustomUser model and create or update the UserAddress models. I have figured out how to mash together the functionality using the base View CBV, but I suspect I'm duplicating a lot of functionality that I could probably find already done in Django. This is my view, where I handle the form instantiating manually, and feed the context.
class UserDetailsView(View):
def get(self, request):
user = request.user
user_basic = CustomUser.objects.get(pk=user.pk)
basic_form = UserBasicForm(instance=user_basic)
user_address = UserAddress.objects.get(user=user.pk)
billing_address_form = UserAddressForm(instance = user_address)
context = {'basic_form':basic_form,'billing_address_form':billing_address_form}
return render(request, 'mainapp/profile.html', context)
My post is the same at this point, as I haven't done the actual validation and saving yet.
class UserBasicForm(forms.ModelForm):
class Meta(forms.ModelForm):
model = CustomUser
fields = (
'username',
'first_name',
'last_name',
)
labels = {
'username':'Username',
'first_name':'First Name',
'last_name':'Last Name',
}
class UserAddressForm(forms.ModelForm):
class Meta(forms.ModelForm):
model = UserAddress
fields = (
'description',
'addressee',
'company',
'address_1',
'address_2',
'city',
'prov_state',
'post_zip',
'country',
)
labels = {
'description':'Address Description',
'addressee':'Addressee',
'company':'Company Name',
'address_1':'Address',
'address_2':'Address 2',
'city':'City',
'prov_state':'Province or State',
'post_zip':'Postal or Zip Code',
'country':'Country',
}
class CustomUser(AbstractUser):
objects = CustomUserManager()
def __str__(self):
return self.email
class UserAddress(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE,)
description = models.CharField(max_length = 256, default='Description')
addressee = models.CharField(max_length = 256,)
company = models.CharField(max_length = 256, default='Self')
address_1 = models.CharField(max_length = 256,)
address_2 = models.CharField(max_length = 256,)
city = models.CharField(max_length = 256,)
prov_state = models.CharField(max_length = 256,)
post_zip = models.CharField(max_length = 256,)
country = models.CharField(max_length = 256,)
def __str__(self):
return self.description
Please go easy on me, I'll take most advice offered.
Edit
After reviewing some other SO questions and Django form examples, it appears that the final answer probably isn't SO material. That said, my observation is that for the Django built-in CBVs, the "best" base view is that which you can minimize or simplify the code you add. Using a TemplateView or FormView for my project in this case just depends on which methods I choose to re-write or override and for that, I'm still open to suggestions.
I'd do something like this (with betterforms):
class UserCreationMultiForm(MultiModelForm):
form_classes = {
'basic': UserBasicForm,
'address': UserAddressForm,
}
class UserDetailsView(View):
template = "mainapp/profile.html"
form_class = UserCreationMultiForm
success_url = reverse_lazy('home')
def form_valid(self, form):
# Save the user first, because the profile needs a user before it
# can be saved.
user = form['basic'].save()
address = form['address'].save(commit=False)
address.user = user
address.save()
return redirect(self.get_success_url())
Then rename your forms in your template to form.basic and form.address
I have a customer model in Bcustomer app that extends the django User model, So I will save the basic details such as name in User table and the remaining data (city, etc) in customer table.
When I call the below code through API, it shows the following error. But data is saving in the tables. I also want to implement the get and put calls for this api.
Got AttributeError when attempting to get a value for field `city` on serializer `CustomerSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `User` instance.
Original exception text was: 'User' object has no attribute 'city'.
my Bcustomer/models.py
class BCustomer(models.Model):
customer = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True, blank=True )
address = models.CharField(max_length=50)
city = models.CharField(max_length=256)
state = models.CharField(max_length=50)
user = models.ForeignKey(settings.AUTH_USER_MODEL, db_index=True, on_delete=models.CASCADE, related_name='customer_creator')
# more fields to go
def __str__(self):
# return str(self.name) (This should print first and last name in User model)
class Meta:
app_label = 'bcustomer'
my Bcustomer/serializers.py
from django.contrib.auth import get_user_model
from models import BCustomer
class CustomerSerializer(serializers.HyperlinkedModelSerializer):
city = serializers.CharField()
class Meta:
model = get_user_model()
fields = ('first_name', 'email','city')
def create(self, validated_data):
userModel = get_user_model()
email = validated_data.pop('email', None)
first_name = validated_data.pop('first_name', None)
city = validated_data.pop('city', None)
request = self.context.get('request')
creator = request.user
user = userModel.objects.create(
first_name=first_name,
email=email,
# etc ...
)
customer = BCustomer.objects.create(
customer=user,
city=city,
user=creator
# etc ...
)
return user
my Bcustomer/views.py
class CustomerViewSet(viewsets.ModelViewSet):
customer_photo_thumb = BCustomer.get_thumbnail_url
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
queryset = BCustomer.objects.all()
serializer_class = CustomerSerializer
my Bcustomer/urls.py
router.register(r'customer', views.CustomerViewSet, 'customers')
POST request format
{
"first_name":"Jsanefvf dss",
"city":"My City",
"email":"myemail#gmail.com",
#more fields
}
I also need to implement put and get for this api. Now data is saving in both tables but shows the error.
Sure it complains.
Validation goes well, so does the creation but once it's created, the view will deserialize the result and return it to the client.
This is the point where it goes south. Serializer is configured to think that city is a field of the default user while it actually is part of BCustomer. In order to work this around, you should set the source argument to the city field. You might need to update the serializer's create/update to reflect that change, not sure about that one.