Django Model - Charfield with choices from a row in another model? - python

I have created a settings table in Django as per the below:-
class Settings(models.Model):
name = models.CharField(max_length=200)
class Meta:
verbose_name = "Settings"
verbose_name_plural = "Settings"
def __str__(self):
return self.name
class SettingChoices(models.Model):
setting = models.ForeignKey(Settings, on_delete=models.PROTECT)
choice = models.CharField(max_length=200)
class Meta:
verbose_name = "Setting Choices"
verbose_name_plural = "Setting Choices"
def __str__(self):
return '{0} - {1}'.format(self.setting, self.choice)
and a sample use of this would be:-
Setting = Circuit Type:
Choices:
DSL
4G
Fibre
then in another model I want to be able to reference this as set of choices
class Circuits(models.Model):
site_data = models.ForeignKey(SiteData, verbose_name="Site", on_delete=models.PROTECT)
order_no = models.CharField(max_length=200, verbose_name="Order No")
expected_install_date = models.DateField()
install_date = models.DateField(blank=True, null=True)
circuit_type = models.CharField(max_length=100, choices=*** here I would get model settings - Circuit Type - Choices ***)
currently I use a list in settings.py but its not fluid, I need my users to be able to alter these settings not for me to manually edit a list in settings.py and push changes each time
I attempted the below:
functions.py
def settings_circuit_types():
from home.models import SettingChoices
type_data = SettingChoices.objects.filter(setting__name='CIRCUIT_TYPES')
circuit_types = []
for c in type_data:
circuit_types.append(c.choice)
return circuit_types
models.py
from app.functions import settings_circuit_types
CIRCUIT_CHOICES = settings_circuit_types()
...
circuit_type = models.CharField(max_length=100, choices= CIRCUIT_CHOICES)
but this has thrown an error
dango.core.exceptions.AppRegistryNotReady: Models aren't loaded yet.
which is understandable, im wondering if what im trying to achieve is possible by other means?
Thanks

So this is a better way of doing this as i mentioned it in comment section:
1 - You don't need Settings and SettingChoices. They are basically the same so you can combine them into one model called Setting:
class Setting(models.Model):
name = models.CharField(max_length=200)
# if you need anything that you might think you need another model for,
# just think this way, add a BooleanField.
# for example if you have a setting available only for admins:
only_admin = models.BooleanField(default=False)
# then when you're going to make a from, just filter the options;
# options = setting.objects.filter(only_admin=False)
class Meta:
verbose_name = "Settings"
verbose_name_plural = "Settings"
def __str__(self):
return self.name
2 - And for Circuits model you just need a simple ForeignKey field:
class Circuits(models.Model):
site_data = models.ForeignKey(SiteData, verbose_name="Site", on_delete=models.PROTECT)
order_no = models.CharField(max_length=200, verbose_name="Order No")
expected_install_date = models.DateField()
install_date = models.DateField(blank=True, null=True)
circuit_type = models.ForeignKey(Setting, null=False, blank=False)
Now when you want to make a form for users to fill:
forms.py:
class CircuitsForm(forms.ModelForm):
class Meta:
model = Circuits
fields = ('install_date', 'circuit_type') # or other fields.
# and to filter which choices are available to choose from:
def __init__(self, *args, **kwargs):
super(CircuitsForm, self).__init__(*args, **kwargs)
self.fields["circuit_type"].queryset = setting.objects.filter(only_admin=False)
This way you have a safe and easy way to make forms for both users and your admins.
You can edit the admin panel itself or just make a url just for admin users with a form like this.
Also if you aren't that kind of people who uses django to render their form you can simply get the available choices in your view and pass it to the template like this:
settings = setting.objects.filter(only_admin=False)
and render it in a template like this:
<select name="circuit_type">
{% for setting in settings %}
<option value="{{ setting.pk }}">{{ setting.name }}</option>
{% endfor %}
</select>
Now you will have only choices you want them to show up in form and even if user tries to mess with template code and add more options, form won't allow them to be accepted and it will raise an error for it.

Related

Django Forms: How to make form.fields by Model.QuerySet in dynamic?

I need to generate Django forms.Form object with fields not from Model.fields (Database Table Columns names), but by records in Model.Table.
I have table Model in models.py:
class MntClasses(models.Model):
type = models.CharField(max_length=2, blank=True, null=True)
class_subtype = models.CharField(max_length=45, blank=True, null=True)
text = models.CharField(max_length=45, blank=True, null=True)
explanation = models.CharField(max_length=45, blank=True, null=True)
name = models.CharField(max_length=45, blank=True, null=True)
views.py
# Form generate
class Form_classes(forms.Form):
def __int__(self, *args, **kwargs,):
super(Form_classes, self).__init__(*args, **kwargs)
print("some")
for fld_ in args:
self.fields[fld_.name] = forms.BooleanField(label=fld_.text)
#Main
def page_Category_Main(request, post):
db_table = MntClasses
form_fld = db_table.objects.all()
'''
This QuerySet 20 records returned of <MntClasses: MntClasses object (0-19)> type.
QuerySet Filds Names: 'name','type','expalnation', 'text'
''':
form_ = Form_classes(*form_fld)
exit_ = {
'form': form_,
}
return render(request, template_name="category.html", context=exit_)
It raise TypeError
init() takes from 1 to 12 positional arguments but 20 were given
So, i have no idea what does it mean this code taken from were: Auto-generate form fields for a Form in django:
def __int__(self, *args, **kwargs,):
super(Form_classes, self).__init__(*args, **kwargs)
What is this "*args", how to use it?
How can I generate Form.fields by QuerySet form_fld.name in that case?
About args
To understand what args is you can take a look at this post which will eventually direct you to https://docs.python.org/3/tutorial/controlflow.html#arbitrary-argument-lists.
Basically it's a special python syntax which allows a function to retrieve multiple arguments as a tuple in a single variable.
About Django's Models
Do you really want to generate multiple forms?
If this is the case you would need to loop over your table:
forms = []
db_table = MntClasses
for item in db_table.objects.all():
forms.append(Form_classes(item))
exit_ = {
'form': forms,
}
the trick is then how you deal with the multiple forms on the front end.
Also you probably want to switch to a ModelForm which would look something like:
from django import forms
from myapp.models import MntClasses
class FormMnt(forms.ModelForm):
class Meta:
model = MntClasses
...
In case you want to handle multiple instances of MntClasses in a single form, you should look at Django's formsets.

Pulling several Django models together into a single list

I have a MySQL database with four related tables: project, unit, unit_equipment, and equipment. A project can have many units; a unit can have many related equipment entries. A single unit can only belong to one project, but there is a many-to-many between equipment and unit (hence the unit_equipment bridge table in the DB). I'm using Django and trying to create a view (or a list?) that shows all 3 models on the same page, together. So it would list all projects, all units, and all equipment. Ideally, the display would be like this:
Project --------- Unit ------------- Equipment
Project 1 first_unit some_equipment1, some_equipment2
Project 1 second_unit more_equipment1, more_equipment2
Project 2 another_unit some_equipment1, more_equipment1
Project 2 and_another_unit some_equipment2, more_equipment2
but at this point I'd also be happy with just having a separate line for each piece of equipment, if comma-separating them is a pain.
Although it seems straightforward to create a form where I can add a new project and add related unit and equipment data (using the TabularInline class), I cannot for the life of me figure out how to bring this data together and just display it. I just want a "master list" of everything in the database, basically.
Here's the code I have so far:
models.py
class Project(models.Model):
name = models.CharField(max_length=255, blank=True, null=True)
class Meta:
managed = False
db_table = 'project'
def __str__(self):
return self.name
class Unit(models.Model):
project = models.ForeignKey(Project, models.DO_NOTHING, blank=True, null=True)
name = models.CharField(max_length=255, blank=True, null=True)
class Meta:
managed = False
db_table = 'unit'
def __str__(self):
return self.name
class UnitEquipment(models.Model):
unit = models.ForeignKey(Unit, models.DO_NOTHING, blank=True, null=True)
equipment = models.ForeignKey(Equipment, models.DO_NOTHING, blank=True, null=True)
class Meta:
managed = False
db_table = 'unit_equipment'
class Equipment(models.Model):
name = models.CharField(max_length=100, blank=True, null=True)
description = models.CharField(max_length=255, blank=True, null=True)
class Meta:
managed = False
db_table = 'equipment'
def __str__(self):
return self.name
views.py
def project_detail_view(request):
obj = Project.objects.all()
context = {'object': obj}
return render(request, "project/project_detail.html", context)
urls.py
urlpatterns = [
path('project/', project_detail_view),
path('', admin.site.urls),
]
admin.py
class UnitTabularInLine(admin.TabularInline):
model = Unit
extra = 0
class ProjectAdmin(admin.ModelAdmin):
inlines = [UnitTabularInLine]
class Meta:
model = Project
# a list of displayed columns name.
list_display = ['name']
# define search columns list, then a search box will be added at the top of list page.
search_fields = ['name']
# define filter columns list, then a filter widget will be shown at right side of list page.
list_filter = ['name']
# define model data list ordering.
ordering = ('name')
I think I need to somehow add more entries to the list_display in the admin file, but every time I try to add unit or equipment it throws an error. I've also tried adding more attributes to Project, but I can't seem to get the syntax right, and I'm never sure which model class I'm supposed to make it.
I've also looked at FormSets, but I cannot get my head around how to alter my current code to get it to work.
How do I get these models together into a unified view?
You don't need to edit the admin view to add your own view: which you may find you are able to do in this case to get your data displayed exactly as you want.
If you do want to show the related object values in the admin list, then you can use lookups and custom columns: however in this case your list would be based upon the Unit.
# You don't need an explicit UnitEquipment model here: you can
# use a simple ManyToManyField
class Unit(models.Model):
project = ...
name = ...
equipment = models.ManyToManyField(Equipment, related_name='units')
def equipment_list(admin, instance):
return ', '.join([x.name for x in instance.equimpent.all()])
class UnitAdmin(admin.ModelAdmin):
class Meta:
model = Unit
list_display = ['project__name', 'name', equipment_list]
def get_queryset(self, request):
return super().get_queryset(request)\
.select_related('project')\
.prefetch_related('equipment')
Note that you need to have the queryset override, otherwise there will be a bunch of extra queries as each unit also requires fetching the project and list of equipment for that unit.
There's also a further improvement you can make to your queries: you could aggregate the related equipment names using a Subquery annotation, and prevent the second query (that fetches all related equipment items for the units in the queryset). This would replace the prefetch_related()
Thanks to #Matthew Schinckel, I was able to find my way to the answer. Here's what my files look like now (only edited the Unit class in models.py):
models.py
class Unit(models.Model):
project = models.ForeignKey(Project, models.DO_NOTHING, blank=True, null=True)
name = models.CharField(max_length=255, blank=True, null=True)
equipment = models.ManyToManyField(Equipment, related_name='units')
class Meta:
managed = False
db_table = 'unit'
def __str__(self):
return self.name
def equipment_list(self):
return ', '.join([x.name for x in self.equipment.all()])
admin.py
class UnitAdmin(admin.ModelAdmin):
class Meta:
model = Unit
# a list of displayed columns name.
list_display = ('project', 'name', 'equipment_list')
# define search columns list, then a search box will be added at the top of list page.
search_fields = ['project']
# define filter columns list, then a filter widget will be shown at right side of list page.
list_filter = ['project', 'name']
# define model data list ordering.
ordering = ('project', 'name')
def get_queryset(self, request):
return super().get_queryset(request)\
.select_related('project')\
.prefetch_related('equipment')
So the changes I made were:
1. Make list_display a tuple instead of a list.
2. Throw def equipment_list(self) into the Unit class (so it's callable as an attribute of Unit) and pass (self) instead of (admin, instance) (I kept getting an error that was looking for the instance argument).

Slow loading Django Admin change/add

I'm making a classic single view application, mapping multiple
datasources.
Django-admin is paginated so there's no impact when I view my list, the problem is when I want to change/add it is .
Using the debug-toolbar my queries look fine, I don't think they take a long time.
I tried to use a suggestion here Django admin change form load quite slow and created a form, but this had no impact.
When is use exclude = ['e_vehicle','e_product'] it's no surprise that add/change screens load instantly.
Any thoughts please
model.py
class Product_Mapping(Trackable):
product_mapping_id = models.AutoField(primary_key=True)
s_product = models.OneToOneField(sProduct, on_delete=models.CASCADE)
e_fund_manager = models.ForeignKey(eManager, models.DO_NOTHING, blank=True, null=True)
e_product = models.ForeignKey(eProduct, models.DO_NOTHING, blank=True, null=True)
e_vehicle = models.ForeignKey(eVehicle, models.DO_NOTHING, blank=True, null=True)
eManager has around 3K
eProduct has around 17K (has fkey to eManager)
eVehicle has around 25K (has fkey to eProduct)
form.py
class MappingProductForm(forms.ModelForm):
s_product = forms.ChoiceField(required=False,
choices=sProduct.objects.values_list('final_publications_product_id', 'product_name'))
e_fund_manager = forms.ChoiceField(required=False,
choices=eManager.objects.values_list('e_fund_manager_id', 'manager_name'))
e_product = forms.ChoiceField(required=False,
choices=eProduct.objects.values_list('e_product_id', 'product_name'))
e_vehicle = forms.ChoiceField(required=False,
choices=eVehicle.objects.values_list('e_vehicle_id', 'formal_vehicle_name'))
class Meta:
model = Product_Mapping
fields = '__all__'
admin.py
#admin.register(Product_Mapping)
class ChampProductMappingAdmin(admin.ModelAdmin):
form = MappingProductForm
It can be seen that there are too many values in e_product and e_vehicle tables. You are using ChoiceField it means inside HTML dropdown there would be ~17K-25K options this would slow down rendering and sometimes hang on client side.
Solution
edit your forms.py
from django.contrib.admin.widgets import ForeignKeyRawIdWidget
class MappingProductForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MappingProductForm, self).__init__(*args, **kwargs)
rel_model = self.Meta.model
rel_eprod = rel_model._meta.get_field('e_product').rel
rel_eprod = rel_model._meta.get_field('e_vehicle').rel
self.fields['e_product'].widget = ForeignKeyRawIdWidget(rel_eprod, admin_site=admin.site)
self.fields['e_vehicle'].widget = ForeignKeyRawIdWidget(rel_eprod, admin_site=admin.site)
s_product = forms.ChoiceField(required=False,
choices=sProduct.objects.values_list('final_publications_product_id', 'product_name'))
e_fund_manager = forms.ChoiceField(required=False,
choices=eManager.objects.values_list('e_fund_manager_id', 'manager_name'))
e_product = forms.ModelChoiceField(required=False,
choices=eProduct.objects.all())
e_vehicle = forms.ModelChoiceField(required=False,
choices=eVehicle.objects.all()
)
class Meta:
model = Product_Mapping
fields = '__all__'
This would keep raw id as widget. You can get something like below.
You can add other entries by hitting search button besides input.
This is better option when you have lots of choices in ModelChoiceField

Django: MultiChoiceField does not show saved choices added after creation

I am currently trying to create a dynamic product model that will allow admins to create add their own "option sets" to products.
For example, Product A has flap valve with 400mm, 500mm and 600mm widths available.
To facilitate this I have created 3 models.
models.py
# A container that can hold multiple ProductOptions
class ProductOptionSet(models.Model):
title = models.CharField(max_length=20)
# A string containing the for the various options available.
class ProductOption(models.Model):
value = models.CharField(max_length=255)
option_set = models.ForeignKey(ProductOptionSet)
# The actual product type
class HeadwallProduct(Product):
dimension_a = models.IntegerField(null=True, blank=True)
dimension_b = models.IntegerField(null=True, blank=True)
# (...more variables...)
flap_valve = models.CharField(blank=True, max_length=255, null=True)
...and a form...
forms.py
class HeadwallVariationForm(forms.ModelForm):
flap_valve = forms.MultipleChoiceField(required=False, widget=forms.SelectMultiple)
def __init__(self, *args, **kwargs):
super(HeadwallVariationForm, self).__init__(*args, **kwargs)
self.fields['flap_valve'].choices = [(t.id, t.value) for t in ProductOption.objects.filter(option_set=1)]
def save(self, commit=True):
instance = super(HeadwallVariationForm, self).save(commit=commit)
return instance
class Meta:
fields = '__all__'
model = HeadwallProduct
This works fine for during the initial creation of a product. The list from the MultipleChoiceForm is populated with entries from the ProductOptionSet and the form can be saved.
However, when the admin adds a 700mm flap valve as an option to the ProductOptionSet of Product A things fall apart. Any new options will show up in the admin area of the existing product - and will even be persisted to the database when the product is saved - but they will not be shown as selected in the admin area.
If a Product B is created the new options work as intended, but you cannot add new options to an existing product.
Why does this happen and what can I do to fix it? Thanks.
Urgh... after about 4 hours I figured it out...
Changing:
class ProductOption(models.Model):
value = models.CharField(max_length=20)
option_set = models.ForeignKey(ProductOptionSet)
to
class ProductOption(models.Model):
option_value = models.CharField(max_length=20)
option_set = models.ForeignKey(ProductOptionSet)
Fixed my issue.

Loop and Print Verbose_Name of all tables in Django's Model.py

I have a situation where I would like to loop over all the tables available in database(stored in models.py) and print the meta attribute verbose_name only for tables having this attribute in the django html template. .This way the user can select the tables he want to view.
I have tried a few methods, but none seem to work..
This is my code :-
def tables_select(request):
if request.user.is_authenticated():
mdls = models.get_models(include_auto_created=False)
tables_list = [mdl._meta.verbose_name for mdl in mdls]
return render(request, 'cbm/tables_select.html', {'tables_list': tables_list})
else :
...
a check using hasattr() method doesn't work either :
for mdl in mdls:
if (hasattr(mdl._meta, 'verbose_name')):
tables_list.append(mdl._meta.verbose_name)
For the tables not having the verbose_name parameter, both of them return actual db table name.
UPDATE: These are the 2 model definitions in models.py :
This is the master table
class Ai(models.Model):
id = models.AutoField(db_column='ID', primary_key=True)
well = models.ForeignKey('Well', db_column='Well_ID')
samplename = models.CharField(db_column='sampleName', max_length=40, blank=True)
startdepth = models.FloatField(db_column='startDepth', blank=True, null=True)
... # Plus some other Columns
def __unicode__(self):
return self.samplename
class Meta:
managed = False
db_table = 'ai'
verbose_name = "Adsorbtion Isotherm"
AichIo is a dependent table on Ai
class Aich4Io(models.Model):
id = models.AutoField(db_column='ID', primary_key=True)
ai = models.ForeignKey(Ai, db_column='AI_ID')
pressurekpa = models.IntegerField(db_column='pressureKPa', blank=True, null=True)
pressureatm = models.FloatField(db_column='pressureAtm', blank=True, null=True)
meccreported = models.FloatField(db_column='meccReported', blank=True, null=True)
dafccreported = models.FloatField(db_column='dafccReported', blank=True, null=True)
class Meta:
managed = False
db_table = 'aich4io'
I don't want the names of dependent tables to be printed.
However for my html template:
{% for table in tables_list %}
{{ table }}<br>
{% endfor %}
The output is:
Adsorbtion Isotherm
aich4 io
After much research/combinations was able to find out this is generic behavior of django.
For models not having verbose_name parameter, the table/model name is returned as string.
To achieve my objective, I added verbose_name = 'DNS' for the models for which I didn't want to display the verbose_name. And in my code i did something like:
if mdl._meta.verbose_name == 'DNS':
# Do Nothing
else:
# My Logic.
You can check what type the verbose name is. If it's unicode then you can print the name
mdls = models.get_models(include_auto_created=False)
for mdl in mdls:
if isinstance(mdl._meta.verbose_name, unicode):
print mdl._meta.verbose_name

Categories