I have the following models:
class Product(models.Model):
active = models.BooleanField(default=True)
name = models.CharField(max_length=40, unique=True)
acronym = models.CharField(max_length=3, unique=True)
bool1_name = models.CharField(max_length=60, blank=True, null=True)
bool1_default = models.BooleanField(default=False)
int1_name = models.CharField(max_length=60, blank=True, null=True)
int1_default = models.IntegerField(blank=True, null=True)
float1_name = models.CharField(max_length=60, blank=True, null=True)
float1_default = models.FloatField(blank=True, null=True)
date1_name = models.CharField(max_length=60, blank=True, null=True)
class ProductData(models.Model):
created = models.DateTimeField(default=datetime.now)
created_by = models.ForeignKey(User)
item = models.ManyToManyField(Item)
bool1_val = models.BooleanField(default=False)
int1_val = models.IntegerField(blank=True, null=True)
float1_val = models.FloatField(blank=True, null=True)
date1_val = models.DateField(blank=True, null=True)
class Item(models.Model):
product = models.ForeignKey(Product)
business = models.ForeignKey(Business)
And I have put in the following data into the database:
# pseudo code
Product(1,'Toothpaste','TP','Is the toothpaste white?',1,,,'Weight',1,)
Product(1,'Milk','MLK',,,,,'Litres',2,'Best Before')
I want to be able to build a form for ProductData based on the variables defined in Product (create-a-form-based-on-the-values-from-another-table). I want something like this:
class ProductDataForm(ModelForm):
def __init__(self,p,*args,**kwargs):
super(ProductDataForm, self).__init__(*args, **kwargs)
# if the values isn't set in the product
if p.bool1_name is None:
# hide the input
self.fields['bool1_val'].widget = forms.CharField(required=False)
else:
# else make sure the name the field name is the product name
self.fields['bool1_val'].widget = forms.BooleanField(label=p.bool1_name)
...
But I'm having a problem passing an instance of Product to ProductDataForm. Others have said I could use a BaseModelFormSet but literature on this is sketchy and I'm not to sure how to apply it.
EDIT
If I create an array of all the fields I DON'T want to show in ProductDataForm's init how can I pass these to the Meta class's exclude. Like so:
class ProductDataForm(ModelForm):
def __init__(self,p,*args,**kwargs):
super(ProductDataForm, self).__init__(*args, **kwargs)
tempExclude = []
if not p.bool1_name:
tempExclude.append('bool1_val')
else:
self.fields['bool1_val'].label = p.bool1_name
self.Meta.exclude = tempExclude
class Meta:
model = ProductData
exclude = []
EDIT
I'm now trying to store the fields I want to exclude in the setting.py file like so:
# settings.py
SUBITEM_EXCLUDE_FIELDS = ['dave']
# views.py
def new_product_data_view(request,product='0'):
try:
i_fpKEY = int(product)
except ValueError:
raise Http404()
if not i_fpKEY:
t_fp = Product.objects.filter(active=1).order_by('id')[0]
else:
t_fp = Product.objects.get(id=i_fpKEY)
FieldsToExcludeFromProductDataForm(t_fp)
print "views.py > PRODUCTDATA_EXCLUDE_FIELDS = "+str(PRODUCTDATA_EXCLUDE_FIELDS)
siForm = ProductDataForm(t_fp, request.POST, auto_id='si_%s')
return render_to_response(...)
# middleware.py
def FieldsToExcludeFromProductDataForm(tempFP):
excludedFields = ['created','created_by','item']
if not tempFP.bool1_name:
excludedFields.append('bool1_val')
if not tempFP.int1_name:
excludedFields.append('int1_val')
...
for val in excludedFields:
PRODUCTDATA_EXCLUDE_FIELDS.append(val)
print "middleware.py > PRODUCTDATA_EXCLUDE_FIELDS = "+str(PRODUCTDATA_EXCLUDE_FIELDS)
# forms.py
class ProductDataForm(ModelForm):
# Only renames the fields based on whether the product has a name
# for the field. The exclusion list is made in middleware
def __init__(self,fp,*args,**kwargs):
super(ProductDataForm, self).__init__(*args, **kwargs)
if fp.bool1_name:
self.fields['bool1_val'].label = fp.bool1_name
if fp.int1_name:
self.fields['int1_val'].label = fp.int1_name
class Meta:
def __init__(self,*args,**kwargs):
super(Meta, self).__init__(*args, **kwargs)
print 'Meta > __init__ > PRODUCTDATA_EXCLUDE_FIELDS = '+str(PRODUCTDATA_EXCLUDE_FIELDS)
model = ProductData
print 'Meta > PRODUCTDATA_EXCLUDE_FIELDS = '+str(PRODUCTDATA_EXCLUDE_FIELDS)
#exclude = PRODUCTDATA_EXCLUDE_FIELDS
But terminal shows that the Meta class gets processed very early on and therefore can't get the newly amended PRODUCTDATA_EXCLUDE_FIELDS :
Meta > PRODUCTDATA_EXCLUDE_FIELDS = ['dave']
[11/Jul/2011 15:51:31] "GET /page/profile/1/ HTTP/1.1" 200 11410
middleware.py > PRODUCTDATA_EXCLUDE_FIELDS = ['dave', 'created', 'created_by', 'item', 'bool1_val', 'int1_val']
views.py > PRODUCTDATA_EXCLUDE_FIELDS = ['dave', 'created', 'created_by', 'item', 'bool1_val', 'int1_val']
[11/Jul/2011 15:51:32] "GET /item/new/ HTTP/1.1" 200 5028
[11/Jul/2011 15:51:32] "GET /client/1/ HTTP/1.1" 200 5445
[11/Jul/2011 15:51:32] "GET /client/view/1/ HTTP/1.1" 200 3082
Why bother with exclude, and metaclasses - just delete fields you want to exclude:
class ProductDataForm(ModelForm):
__init__():
...
for field_name in PRODUCTDATA_EXCLUDE_FIELDS:
del self.fields[field_name]
...
Maybe it's not quite right, but it's simple, and it works.
Your idea was spot on but then you tried to implement it in a very roundabout way.
Python lets you create classes dynamically at runtime using the three argument form of the type() function.
The common way to make use of this is with a factory function and Django provides just that in the form of django.forms.models.modelform_factory.
This function isn't documented but that seem to be in progress. It's from the same family of functions as modelformset_factory and inlineformset_factory which are documented so I'd say it's safe to use.
The signature is
modelform_factory(model, form=ModelForm, fields=None, exclude=None, formfield_callback=None)
model, exclude and fields are equivalent to what you would normally declare in the form's inner Meta class (and that's how it's implemented).
Now, to use this in your example you'll want to change your approach a bit. First import the factory function.
from django.forms.models import modelformset_factory
Then use your view function or class to:
Get the product instance.
Generate a list of excluded fields based on the product instance (I'll call this excludedFields).
Create a form class:
formClass = modelform_factory(ProductData, excludes=excludedFields)
Initialise your form as usual, but using formClass instead of a predefined form (ProductDataForm)
form = formClass() or form = formClass(request.POST) etc...
Related
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.
I'm working on a project where they have various job types that I've tackled with CHOICES, however, I want to add conditionals for WHEN job type 1 is chosen, SUBTYPES x-y become choices. I am having trouble with the syntax of how you would do that. I've included my pseudocode below... I appreciate any help!
from django.db import models
class User(models.Model):
name = models.CharField(max_length=255)
def __str__(self):
return self.name
class Job(models.Model):
name = models.CharField(max_length=255)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='jobs')
JOB_CHOICES = (
('carpentry', 'Carpentry'),
('cleaning', 'Cleaning'),
('electrician', 'Electrician'),
('handyman', 'Handyman'),
('hvac', 'HVAC'),
('painting', 'Painting'),
('pest', 'Pest'),
('plumbing', 'Plumbing'),
('roofing', 'Roofing'),
('property', 'Property'),
)
jobType = models.CharField(max_length=30, choices=JOB_CHOICES, default='handyman')
# If JobType = Carpentry:
# CARPENTRY_CHOICES = (
# ('trim', 'trim')
# ('sheetrock', 'Sheetrock')
# ('windows', 'Windows')
# ('doors', 'Doors')
# ('cabinets and shelving', 'Cabinets and Shelving')
# ('other', 'Other')
# )
# jobType = models.CharField(max_length=30, choices=CARPENTRY_CHOICES, default='other')
def __str__(self):
return self.name
Django Models
Django Serializer
/api editor
I would probably go with a job_type model, which has a name and a 'subtype' field.
class JobType(models.Model):
SubTypeChoices = (...)
name = models.CharField()
subtype = models.CharField(choices=SubTypeChoices, ...)
class Job(models.Model):
....
job_type = models.ForeignKey(JobType, ...)
....
This way you can associate your 'subtypes' with one job_type. And if for some reason you can have several job_types for a Job, use a ManyToMany field.
I'm creating a web application with Django.
In my models.py I have a class BaseProduct and a class DetailProduct, which extends BaseProduct.
In my admin.py I have BaseProductAdmin class and DetailProductAdmin class, which extends BaseProductAdmin.
I have another class called System, with a many to many relation with BaseProduct.
In the System admin page, I can visualize a list of the BaseProduct objects related to that system.
When I click on a product, the application redirect me to the BaseProduct admin page.
When a product of the list is a DetailProduct object, I would like to be redirected on the DetailProduct admin page instead.
Any idea on how to do this?
In models.py :
class BaseProduct(models.Model):
id = models.AutoField(primary_key=True, db_column='ID')
_prod_type_id = models.ForeignKey(
ProductTypes, verbose_name="product type", db_column='_prod_type_ID')
systems = models.ManyToManyField(
'database.System', through='database.SystemProduct')
def connected_to_system(self):
return self.systems.exists()
class Meta:
db_table = u'products'
verbose_name = "Product"
ordering = ['id', ]
class System(models.Model):
id = models.AutoField(primary_key=True, db_column='ID')
name = models.CharField(max_length=300)
def has_related_products(self):
""" Returns True if the system is connected with products. """
return self.products_set.exists()
class Meta:
managed = False
db_table = u'systems'
verbose_name = "System"
ordering = ['id', ]
class DetailProduct(BaseProduct):
options_id = models.AutoField(db_column='ID', primary_key=True)
product = models.OneToOneField(BaseProduct, db_column='_product_ID', parent_link=True)
min_height = models.FloatField(help_text="Minimum height in meters.")
max_height = models.FloatField(help_text="Maximum height in meters.")
def __init__(self, *args, **kwargs):
super(DetailProduct, self).__init__(*args, **kwargs)
if not self.pk:
self._prod_type_id = ProductTypes.objects.get(pk=9)
class Meta:
managed = False
db_table = 'detail_product'
verbose_name = "Detail product"
verbose_name_plural = "Detail products"
class SystemProduct(models.Model):
id = models.AutoField(primary_key=True, db_column='ID')
_system_id = models.ForeignKey(System, db_column='_system_ID')
_product_id = models.ForeignKey(BaseProduct, db_column='_Product_ID')
class Meta:
db_table = u'system_product'
unique_together = ('_system_id', '_product_id')
verbose_name = "system/product connection"
In my admin.py page:
class SystemProductInlineGeneric(admin.TabularInline):
model = SystemProduct
extra = 0
show_edit_link = True
show_url = True
class SystemProductForm(forms.ModelForm):
class Meta:
model = SystemProduct
fields = '__all__'
def __init__(self, *args, **kwargs):
""" Remove the blank option for the inlines. If the user wants to remove
the inline should use the proper delete button. In this way we can
safely check for orphan entries. """
super(SystemProductForm, self).__init__(*args, **kwargs)
modelchoicefields = [field for field_name, field in self.fields.iteritems() if
isinstance(field, forms.ModelChoiceField)]
for field in modelchoicefields:
field.empty_label = None
class SystemProductInlineForSystem(SystemProductInlineGeneric):
""" Custom inline, used under the System change page. Prevents all product-system
connections to be deleted from a product. """
form = SystemProductForm
raw_id_fields = ("_product_id",)
class SystemAdmin(admin.ModelAdmin):
inlines = [SystemProductInlineForSystem]
actions = None
list_display = ('id', 'name')
fieldsets = [('System information',
{'fields': (('id', 'name',), ),}),
]
list_display_links = ('id', 'configuration',)
readonly_fields = ('id',)
save_as = True
If I understand correctly, your question is how to change the InlineAdmin (SystemProductInlineForSystem) template so the "change link" redirects to the DetailProduct admin change form (instead of the BaseProduct admin change form) when the product is actually a DetailProduct.
I never had to deal with this use case so I can't provide a full-blown definitive answer, but basically you will have to override the inlineadmin template for SystemProductInlineForSystem and change the part of the code that generates this url.
I can't tell you exactly which change you will have to make (well, I probably could if I had a couple hours to spend on this but that's not the case so...), so you will have to analyze this part of the code and find out by yourself - unless of course someone more knowledgeable chimes in...
I am completely new to django and was a php coder previously, so please bear with me if i am being dumb.
I have three models defined in my app, Comprehension, Question, Answer. Each comprehension has multiple questions and answers defined as 'inline' in the Comprehension model. Questions are input directly by the admin, but answers would added automatically from the comprehension.
What I want to achieve is, to split the comprehension in to sentences and add each sentence as an answer object with foreignkey of the current comprehension.
I am trying to override the save method in Comprehension model. But when I click save, it gives an instance error
Cannot assign "23L": "Answer.ComprehensionAnswer" must be a "Comprehension" instance.
How do I assign/create and instance here ? or am I following a wrong approach. If so, kindly guide me to the right approach.
Following are the contents of models.py
class Question(models.Model):
QuestionText = models.CharField(max_length=500, verbose_name='Question Text')
QuestionTypeID = models.ManyToManyField(QuestionType, verbose_name='Question Type')
ComprehensionQuestion = models.ForeignKey(Comprehension, verbose_name='comprehension')
QuestionRemarks = models.CharField(max_length=500, verbose_name='remarks', null=True, blank=True)
LastUpdate = models.DateTimeField(auto_now=True)
def __unicode__(self):
return self.QuestionText
def was_published_recently(self):
return self.LastUpdate >= timezone.now() - datetime.timedelta(1)
class Answer(models.Model):
AnswerText = models.CharField(max_length=500, verbose_name='Answer Text')
AnswerTypeID = models.ManyToManyField(AnswerType, verbose_name='Answer Type')
ComprehensionAnswer = models.ForeignKey(Comprehension, verbose_name='Comprehension', null=True, blank=True)
AnswerRemarks = models.CharField(max_length=500, verbose_name='Remarks')
LastUpdate = models.DateTimeField(auto_now=True)
def __unicode__(self):
return self.AnswerText
class Comprehension(models.Model):
ComprehensionTitle = models.CharField(max_length=100, verbose_name='Comprehension Title')
ComprehensionsText = models.TextField(verbose_name='Text')
ComprehensionsRemarks = models.CharField(max_length=400, verbose_name='Remarks for this Comprehension', null=True, blank=True)
LastUpdate = models.DateTimeField("Last Updated", auto_now=True)
def __unicode__(self):
return self.ComprehensionTitle
def was_published_recently(self):
return self.LastUpdate >= timezone.now() - datetime.timedelta(1)
def save(self, *args, **kwargs):
#overrides the default save function to split the comprehension paragraph into sentences and adds them as probable answers
AnswerList = self.ComprehensionsText.split("u'\u0964'")
for sentence in AnswerList:
p = Answer.objects.create(AnswerText = sentence, ComprehensionAnswer = self.pk)
super(Comprehension, self).save(*args, **kwargs)
Content inside admin.py
class ComprehensionAdmin(admin.ModelAdmin):
form = ComprehensionForm
fieldsets = [
('Main', {'fields': ['ComprehensionTitle','ComprehensionsText']}),
('Additional Info', {'fields': ['ComprehensionsRemarks'], 'classes': ['collapse']}),
]
inlines = [QuestionInline, AnswerInline]
list_display = ('ComprehensionTitle', 'LastUpdate')
list_per_page = 10
class QuestionInline(admin.TabularInline):
model = Question
extra = 2
class AnswerInline(admin.TabularInline):
model = Answer
extra = 2
admin.site.register(Question)
admin.site.register(Answer)
admin.site.register(Comprehension, ComprehensionAdmin)
I have also followed the approach mentioned on this page. But, blank about how to create the objects in commit condition using the foreignkey of Comprehension model.
You should use self instead of self.pk and note that self refers the current object.
p = Answer.objects.create(AnswerText = sentence, ComprehensionAnswer = self)
From the traceback, it clearly shows that, ComprehensionAnswer attribute of Answer model expects Comprehension model's object. But you're passing the id of that object.
edit: I completely rewrote the question as the original one didn't clearly explain my question
I want to run a function which is specific to each particular model instance.
Ideally I want something like this:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = models.FunctionField() #stores a function specific to this instance
x = MyModel(data='originalx', perform_unique_action=func_for_x)
x.perform_unique_action() #will do whatever is specified for instance x
y = MyModel(data='originaly', perform_unique_action=func_for_y)
y.perform_unique_action() #will do whatever is specified for instance y
However there is no datatype FunctionField. Normally this would be solvable with inheritance, and creating subclasses of MyModel, maybe like this:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = default_function
class MyModelX(MyModel):
perform_unique_action = function_X
class MyModelY(MyModel):
perform_unique_action = function_Y
x = MyModelX(data='originalx')
x.perform_unique_action() #will do whatever is specified for instance x
y = MyModelY(data='originaly')
y.perform_unique_action() #will do whatever is specified for instance y
Unfortunately, I don't think I can use inheritance because I am trying to access the function this way:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = default_function
class SecondModel(models.Model):
other_data = models.IntegerField()
mymodel = models.ForeignKey(MyModel)
secondmodel = SecondModel.objects.get(other_data=3)
secondmodel.mymodel.perform_unique_action()
The problem seems to be that I don't know what type the foreign key is going to be in SecondModel if I override the perform_unique_action in subclasses.
Can I access MyModel from SecondModel as a foreign key and still have a unique function for each instance of MyModel?
This works for me. I haven't tested it, but you should be able to create another class and override their methods and it'll work. Check the class Meta line, it'll treat it as an abstract class. Here's an example of my actual classes that I'm working on right now.
EDIT: Added VoteComment class and tested it. It works as expected!
class Vote(models.Model):
VOTE_ENUM = (
(VoteEnum.DOWN_VOTE, VoteEnum.toString(VoteEnum.DOWN_VOTE)),
(VoteEnum.NONE, VoteEnum.toString(VoteEnum.NONE)),
(VoteEnum.UP_VOTE, VoteEnum.toString(VoteEnum.UP_VOTE)),
)
question = models.ForeignKey(Question, null=False, editable=False, blank=False)
voter = models.ForeignKey(User, blank=False, null=False, editable=False)
vote_type = models.SmallIntegerField(default=0, null=False, blank=False, choices=VOTE_ENUM)
class Meta:
abstract = True
def is_upvote(self):
return self.vote_type > 0
def is_downvote(self):
return self.vote_type < 0
class VoteAnswer(Vote):
answer = models.ForeignKey(Answer, null=False, editable=False, blank=False)
class Meta:
unique_together = (("voter", "answer"),) # to prevent user from voting on the same question/answer/comment again
def __unicode__(self):
vote_type = "UP" if vote_type > 0 else ("DOWN" if vote_type < 0 else "NONE")
return u"{0}: [{1}] {2}".format(user.username, vote_type, answer.text[:32])
def is_upvote(self):
return "FOO! "+str(super(VoteAnswer, self).is_upvote())
class VoteComment(Vote):
comment = models.ForeignKey(Comment, null=False, editable=False, blank=False)
class Meta:
unique_together = (("voter", "comment"),) # to prevent user from voting on the same question/answer/comment again
def __unicode__(self):
vote_type = "UP" if vote_type > 0 else ("DOWN" if vote_type < 0 else "NONE")
return u"{0}: [{1}] {2}".format(user.username, vote_type, comment.text[:32])
def is_upvote(self):
return "BAR!"
I came up with two ways of having a specific function defined for each object. One was using marshal to create bytecode which can be stored in the database (not a good way), and the other was by storing a reference to the function to be run, as suggested by Randall. Here is my solution using a stored reference:
class MyModel(models.Model):
data = models.CharField(max_length=100)
action_module = models.CharField(max_length=100)
action_function = models.CharField(max_length=100)
class SecondModel(models.Model):
other_data = models.IntegerField()
mymodel = models.ForeignKey(MyModel)
secondmodel_obj = SecondModel.objects.get(other_data=3)
#The goal is to run a function specific to the instance
#of MyModel referred to in secondmodel_obj
module_name = secondmodel_obj.mymodel.action_module
func_name = secondmodel_obj.mymodel.action_function
module = __import__(module_name)
func = vars(module)[func_name]
func()
Thanks to everyone who replied, I couldn't have got to this answer if it weren't for your help.
You could achive some similar behavior overriding the save method. And providing special callbacks to your instances.
Something like:
def default_function(instance):
#do something with the model instance
class ParentModel(model.Model):
data = models.CharField()
callback_function = default_function
def save(self, *args, **kwargs):
if hasattr(self, 'callback_function'):
self.callback_function(self)
super(ParentModel, self).save(*args, **kwargs)
class ChildModel():
different_data = models.CharField()
callback_function = other_fun_specific_to_this_model
instance = ChildModel()
#Specific function to this particular instance
instance.callback_function = lambda inst: print inst.different_data
instance.save()
You can write endpoints on your server and limit their access to just your self. Then store in each model instance corresponding url. For example:
views.py
def funx_x(request):
pass
def func_y(request):
pass
models.py:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = models.URLField()
and then:
x = MyModel(data='originalx', perform_unique_action='http://localhost/funx_x')
requests.post(x.perform_unique_action)
i dont know whether i understand u correct or not. but you can check out this example here.
Example:
A string representing an attribute on the model. This behaves almost the same as the callable, but self in this context is the model instance. Here's a full model example:
class Person(models.Model):
name = models.CharField(max_length=50)
birthday = models.DateField()
def decade_born_in(self):
return self.birthday.strftime('%Y')[:3] + "0's"
decade_born_in.short_description = 'Birth decade'
class PersonAdmin(admin.ModelAdmin):
list_display = ('name', 'decade_born_in')