Django (and database) newbie here.
I'm trying to figure out a way to enable the creation of n custom text fields for a table (let's call it Book) using the admin interface. I would like a way for the user to define new text fields through the admin interface (instead of defining fields like CustomField1, CustomField2, etc, through a model followed by running manage.py syncdb).
Ultimately, I would want to create a separate table called CustomFields. The django admin user (who is not a programmer), would go and enter the custom text fields in this table (e.g. pubdate, isbn, country). Then, when doing data entry for a Book, they would hit "+" for every custom field they wanted, have them available in a dropdown, and add accompanying text for the custom field. The text entered for each field is specific to the parent Book.
Any suggestions? I have a feeling there's a simple solution that I'm somehow not grasping here.
Where you might run into problems is because Django will not recreate tables or columns based on changing model declarations. This means you're unable to add fields to a table at run-time without running the generated sql on your database manually. What you'll need, instead, is a way of defining custom fields, and linking them to your model.
What I'd suggest is the following:
class CustomField(models.Model):
name = models.CharField(max_length=32)
class Book(models.Model):
... fields
class CustomValue(models.Model):
field = models.ForeignKey(CustomField)
value = models.CharField(max_length=255)
book = models.ForeignKey(Book)
The validation on the above is fairly non-existant, and this doesn't allow you to define required custom fields for each model, but you should be able to come up with that part if you need it later on.
# taken and modified from django online tutorial
class CustomValueInline(admin.StackedInline):
model = CustomValue
extra = 3
class BookAdmin(admin.ModelAdmin):
fieldsets = [
# your fields in here
]
inlines = [CustomValueInline]
admin.site.register(Book, BookAdmin)
This will allow users to select up to 3 custom fields and values directly, with the option to add more if they wish.
Edit: Changed the answer to reflect further information in comments.
For the beginning you can create one model for the book and one for the text field, both connected through a foreign key relation. You can easily administrate this then through django's inline admins, which will enable you to add more text fields!
# models.py
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
class TextField(models.Model):
text = models.TextField
book = models.ForeignKey(Book)
# admin.py
from django.contrib import admin
from models import TextField, Book
class TextAdmin(admin.TabularInline):
model = TextField
class BookAdmin(admin.ModelAdmin):
inlines = [TextAdmin]
admin.site.register(Book, BookAdmin)
Related
I need extend a model from another model.
Case:
core/models.py
class Master(models.Model):
code = models.CharField(max_length=30, unique=True)
name = models.CharField(max_length=100, blank=False, null=False)
class Meta:
abstract = True
class City(Master):
zipcode = models.IntegerField()
custom/models.py
from core.models import City
class City(City)
newfield = models.CharField(max_length=20)
custom is an app.
I have tried with proxy model but it is not what I need, since proxy model adds a new table. https://docs.djangoproject.com/en/2.2/topics/db/models/#proxy-models
I need is that when I migrate add the new field to City.
More info.
In core the table is created and in custom you can add new fields that the client needs. The idea is that core is only maintained as standard.
Proxy models don't add new tables. From the docs link you mentioned:
The MyPerson class operates on the same database table as its parent Person class.
If you want one table called core_city, and another called custom_city, the second one having an extra field, you simply subclass it. Perhaps it would be easier to use an alias:
from core.models import City as CoreCity
class City(CoreCity):
newfield = models.CharField(max_length=20)
custom_city will have all fields from core_city, plus a newfield. The description of how this works (and an example) is covered in the docs section Multi-table inheritance.
If what you want is to have one single database table, then you should use a proxy Model, however they really don't allow you to create new fields. The field should be created in the parent model, or otherwise exist in the database and not be handled by Django migrations at all.
You are looking for Abstract base classes models:
Abstract base classes are useful when you want to put some common information into a number of other models. You write your base class and put abstract=True in the Meta class.
This is the base class:
#core/models.py
class City(Master):
zipcode = models.IntegerField()
class Meta:
abstract = True # <--- here the trick
Here your model:
#custom/models.py
from core.models import City as CoreCity
class City(CoreCity):
newfield = models.CharField(max_length=20)
For many uses, this type of model inheritance will be exactly what you want. It provides a way to factor out common information at the Python level, while still only creating one database table per child model at the database level.
You can update or create your class constants after its defined like this
from core.models import City
City.newfield = models.CharField(max_length=20)
You may need to use swappable models, using them you can define a City class and change it with whichever model you need later,
but that way you can't import and use the base City model directly, you may need to provide a method like get_city_model for that, as your public API.
class City(Master):
zipcode = models.IntegerField()
class Meta:
swappable = 'CORE_CITY_MODEL'
and maybe replace it later with some other model, then just set CORE_CITY_MODEL to that model in the form of 'app_name.model_name'.
The django.contrib.auth is a good example of this, you may consider checking User model and get_user_model method. Although I think you may face problems if you change your city model after you did run migrate, it may not move your data to the new table, but I'm not sure about this.
I would like to be able to let users create locations "on-the-fly" when they create a report. Using the following models if possible.
models:
class Report(models.Model):
...
location = forms.ManyToManyField(Location)
class Location(models.Model):
name = forms.CharField(max_length=255)
...
The behavior I am searching for is close to the one given by the django formsets.
Indeed I am currently able to somewhat do that using instead a foreignkey relationship and relying on formsets. Using this technic,django-extra-views and django-dynamic-formset, I am even able to let users choose how many locations they want to add.
A strip down version of the actual setup :
models:
class Report(models.Model):
...
class Location(models.Model):
name = forms.CharField(max_length=255)
report = forms.ForeignKey('Report')
forms:
class LocationForm(forms.ModelForm):
class Meta:
model = Location
fields = '__all__'
class LocationFormSet(extra_views.InlineFormSet):
model = TestLocation
form_class = TestLocationForm
views:
class ReportCreateView(extra_views.CreateWithInlinesView):
model = Report
inlines = [LocationFormSet, ]
Unfortunately using this setup, a location can not belong to more than one report which bring a lot of duplicated location. That's why I think the ManyToMany relationship would be better suited. I stumbled upon some answers related to this question that talked about intermediary model but I wasn't able to go anywhere on my own to make it work.
Is their a way to tweak my working setup to make it work with a manytomany relationship? Or does it exists a completely different approach to let user create "on-the-fly" the locations during the report creation?
Thank's!
For create "on-the-fly" I had changed ManyToMany by ForeingKey.
class Report(models.Model):
...
location = forms.ManyToManyField(Location) # REMOVE
class Location(models.Model):
report = models.ForeignKey(Report) # ADD
name = forms.CharField(max_length=255)
...
And then, InlineForms will work.
Is there a way to add a field to a Django model class such that:
It doesn't get persisted to the database (i.e. no column in the DB)
It does get rendered by a ModelForm
The widget for that field can be customised
I believe 3. can be done with a custom widget, and 2. will happen if the field inherits from models.Field. However, I haven't found a way to achieve 1. without breaking 2. and 3. I was hoping for a persist=False or db_column=None type of solution.
Scenario:
I'm using this to quickly produce data capture forms by only adding a class to the model, but in order to insert headers for sub sections I still having to edit the template. Was hoping to do the following:
models.py
from django.db import models
class Applicant(models.Model):
sectionA = models.SectionField(help_text="Personal details")
title = models.CharField(max_length=100)
name = models.CharField(max_length=100)
sectionB = models.SectionField(help_text="Banking details")
account = models.CharField(max_length=100)
pin = models.CharField(max_length=100)
In the above example, sectionA and sectionB are instances of a custom model.Field that doesn't actually get persisted but cause a heading to be rendered by the ModelForm and a custom widget
Finally:
I realise this probably violates separation of View and Model.
Other questions have been asked about non-persisting fields but their solutions don't render in a ModelForm
Sort of, Just don't make them a model field, theres no need for them to be.
sectionA = "Personal details"
sectionB = "Banking details"
You can access them via form.instance where you need them, you could even make them a form field instead of a string as I've shown here.
I have a Django Model with ManyToManyField in it. I need to require user to select at least one M2M value in this field.
I tried to set blank=False to M2M field but it didn't help.
class Skill(models.Model):
name = models.CharField(max_length=200)
class PersonSkills(models.Model):
person = models.ForeignKey('Person')
skill = models.ForeignKey('Skill')
class Person(models.Model):
name = models.CharField(max_length=200)
skills = models.ManyToManyField('Skill', through='PersonSkills')
p = Person(name='Bob')
p.save()
# success, but I expect that this should throw ValidationError, because I didn't select at least one Skill for this person
I can solve this situation with custom Form definition or with override save() method for Person model.
Is it possible to prevent create Person without at least one Skill selected, with set ManyToManyField options? Or I need to create custom logic to handle this situation? Thanks.
I use Django 1.7 and Python 3.4
Update 1. How to create ModelForm to control M2M? Because in cleaned_data I have only fields that I pass for Person form, and haven't data that I pass as M2M fields. I try to create object in Admin Site and control that Skills selected. I enter Skill's via inline.
# admin.py
class PersonSkillsInline(admin.TabularInline):
model = Person.skills.through
extra = 2
class PersonAdmin(admin.ModelAdmin):
inlines = [PersonSkillsInline]
admin.site.register(Person, PersonAdmin)
On a database level... no, that's not possible. Any enforcement of this will have to come from your application logic.
The reason is that every m2m relation has a record with a foreign key to both sides of the m2m relation. SQL cannot enforce the existence of the referencing side of a relationship, only of the referenced side of a relationship.
Furthermore, you can't enforce it in your model either, because the Person has to be created and saved before you can assign any many-to-many relations.
Your only options are to enforce it in the form or the view.
In an InlineModelAdmin this can easily be done by specifying min_num (1.7+):
class PersonSkillsInline(admin.TabularInline):
model = Person.skills.through
min_num = 1
extra = 2
I have a form in Django called PersonForm this forms model has a one-to-many relationship with Car. When displaying the PersonForm just like in the Django Admin I would like to allow my users to select/deselect from a list of Cars etc. Is this possible? I'm looking for information on where to start.
This is what I have so far for the PersonForm:
class PersonForm(forms.ModelForm):
class Meta:
model = Person
fields = ('description',)
The Models:
class Person(models.Model):
description = models.CharField(max_length="150")
class Car(models.Model):
make = models.CharField(max_length="25")
owner = models.ForeignKey('Person', related_name="Car")
So in the person form I need to show a list of cars that person is the owner of an allow selecting/deselecting of them. I'm assuming I can do this in the form i.e. using something like the related name.
Sounds like you want an inline model form. This give you the ability to add/remove Car objects from a Person within the Person form.
That previous link was for inlinemodeladmin. This next link is for an inline form:
https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#modelforms-factory
I didn't have any chance with inline formset, so i would suggest to override your save method of the model, i feel it's more DRY:
class PersonForm(forms.ModelForm):
# add a field to select a car
car = forms.ModelChoiceField(car.objects.all())
class Meta:
model = Person
fields = ('description', 'car')
def save(self, commit=True):
instance = super().save(commit)
# set Car reverse foreign key from the Person model
instance.car_set.add(self.cleaned_data['car']))
return instance
I know this is an old thread, but since I found myself almost exclusively pointed here by google when searching, I thought I would include the following for anyone else looking for an answer.
The answer, I think, is to use
https://docs.djangoproject.com/en/3.1/ref/forms/fields/#modelchoicefield
or
https://docs.djangoproject.com/en/3.1/ref/forms/fields/#modelmultiplechoicefield
There is a good article on how to use the modelmultiplechoicefield at :
https://medium.com/swlh/django-forms-for-many-to-many-fields-d977dec4b024
But it works for one to many fields as well. These allow us to generate a form with multiple choices as checkboxes or similar widgets based upon a related field in a model.