I'm trying to create a Django model that handles the following:
An Item can have several Names.
One of the Names for an Item is its primary Name, i.e. the Name displayed given an Item.
(The model names were changed to protect the innocent.)
The models.py I've got looks like:
class Item(models.Model):
primaryName = models.OneToOneField("Name", verbose_name="Primary Name",
related_name="_unused")
def __unicode__(self):
return self.primaryName.name
class Name(models.Model):
item = models.ForeignKey(Item)
name = models.CharField(max_length=32, unique=True)
def __unicode__(self):
return self.name
class Meta:
ordering = [ 'name' ]
The admin.py looks like:
class NameInline(admin.TabularInline):
model = Name
class ItemAdmin(admin.ModelAdmin):
inlines = [ NameInline ]
admin.site.register(Item, ItemAdmin)
It looks like the database schema is working fine, but I'm having trouble with the admin, so I'm not sure of anything at this point. My main questions are:
How do I explain to the admin that primaryName needs to be one of the Names of the item being edited?
Is there a way to automatically set primaryName to the first Name found, if primaryName is not set, since I'm using inline admin for the names?
EDIT: Dang, I forgot this was still open. Anyway, I wound up redoing the model, replacing primaryName with just name (a CharField) in Item and renaming Name to Alias. This can't do what I wanted to (just search one table for a name), but I couldn't make the primaryName work if it didn't have null=True, since Item gets saved before any Names were created, meaning that any attempt to auto-assign a Name would see an empty QuerySet and fail.
The only way I could see it working was to have Name's save routine auto-set itself as its parent's primaryName if primaryName was NULL, which just didn't sit well with me.
i think you will want to take a look here.
http://docs.djangoproject.com/en/dev/ref/contrib/admin/#adding-custom-validation-to-the-admin
define your own form to take care of any custom validation that might be required.
How do I explain to the admin that primaryName needs to be one of the
Names of the item being edited?
Check out formfield_for_foreignkey() in the ModelAdmin docs.
class ItemAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "primaryName":
# tweak the filter to your liking.
kwargs["queryset"] = Name.objects.filter(item=...)
return db_field.formfield(**kwargs)
return super(ItemAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
Is there a way to automatically set primaryName to the first Name found,
if primaryName is not set, since I'm
using inline admin for the names?
You could do this in your model's save method, something like:
class Item(models.Model):
...(snip)...
def save(self,force_insert=False,force_update=False):
if self.primaryName is None:
self.primaryName = self.name_set.all()[0]
# will want to handle the case that no names are set, etc
super(Item,self).save(force_insert,force_update)
Related
from model.blah import Ghosts
I have a model has a with a filed looks like this
scary_boos = ArrayField(
choice_char_field(Ghosts.TYPE_SELECTION), blank=True, null=True
)
and in the admin panel, I am trying to add a form to show that field with pre-determined choices.
class GhostBoosForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
print(self.fields["mortgage_type"])
self.fields["scary_boos"].widget = CheckboxSelectMultiple(
choices=self.fields["scary_boos"].choices
)
class Meta:
model = GhostBoos
fields = "__all__"
however choices=self.fields["scary_boos"].choices doesn't work is there any other way to access those choices of the filed?
To do it that way, you'll need to access the base_field attribute of self.fields["scary_boos"]. ArrayField (which looks to be something that only works with PostgreSQL) basically stores it's first argument there, which I think in your case is a normal CharField(?)
More info: https://docs.djangoproject.com/en/2.2/ref/contrib/postgres/fields/#arrayfield
(I had to play around with self.fields a bit to find base_field, but it seems to give you access to what you need).
So: something like self.fields["scary_boos"].base_field.choices[1:] should do what you need. We "slice" off the first result because Django appears to insert the blank option ("", "-----") (at least for me, Django 2.2.7).
Hope that helps.
I want to list only usable items in OneToOneField not all items, its not like filtering values in ChoiceField because we need to find out only values which can be used which is based on the principle that whether it has been used already or not.
I am having a model definition as following:
class Foo(models.Model):
somefield = models.CharField(max_length=12)
class Bar(models.Model):
somefield = models.CharField(max_length=12)
foo = models.OneToOneField(Foo)
Now I am using a ModelForm to create forms based on Bar model as:
class BarForm(ModelForm):
class Meta:
model = Bar
Now the problem is in the form it shows list of all the Foo objects available in database in the ChoiceField using the select widget of HTML, since the field is OneToOneField django will force to single association of Bar object to Foo object, but since it shows all usable and unusable items in the list it becomes difficult to find out which values will be acceptable in the form and users are forced to use hit/trial method to find out the right option.
How can I change this behavior and list only those items in the field which can be used ?
Although this is an old topic I came across it looking for the same answer.
Specifically for the OP:
Adjust your BarForm so it looks like:
class BarForm(ModelForm):
class Meta:
model = Bar
def __init__(self, *args, **kwargs):
super(BarForm, self).__init__(*args, **kwargs)
#only provide Foos that are not already linked to a Bar, plus the Foo that was already chosen for this Bar
self.fields['foo'].queryset = Foo.objects.filter(Q(bar__isnull=True)|Q(bar=self.instance))
That should do the trick. You overwrite the init function so you can edit the foo field in the form, supplying it with a more specific queryset of available Foo's AND (rather important) the Foo that was already selected.
For my own case
My original question was: How to only display available Users on a OneToOne relation?
The Actor model in my models.py looks like this:
class Actor(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name = 'peactor')
# lots of other fields and some methods here
In my admin.py I have the following class:
class ActorAdmin(admin.ModelAdmin):
# some defines for list_display, actions etc here
form = ActorForm
I was not using a special form before (just relying on the basic ModelForm that Django supplies by default for a ModelAdmin) but I needed it for the following fix to the problem.
So, finally, in my forms.py I have:
class ActorForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ActorForm, self).__init__(*args, **kwargs)
#only provide users that are not already linked to an actor, plus the user that was already chosen for this Actor
self.fields['user'].queryset = User.objects.filter(Q(peactor__isnull=True)|Q(peactor=self.instance))
So here I make an ActorForm and overwrite the __init__ method.
self.fields['user'].queryset =
Sets the queryset to be used by the user formfield. This formfield is a ModelChoiceField
by default for a OneToOneField (or ForeignKey) on a model.
Q(peactor__isnull=True)|Q(peactor=self.instance)
The Q is for Q-objects that help with "complex" queries like an or statement.
So this query says: where peactor is not set OR where peactor is the same as was already selected for this actor
peactor being the related_name for the Actor.
This way you only get the users that are available but also the one that is unavailable because it is already linked to the object you're currently editing.
I hope this helps someone with the same question. :-)
You need something like this in the init() method of your form.
def __init__(self, *args, **kwargs):
super(BarForm, self).__init__(*args, **kwargs)
# returns Bar(s) who are not in Foo(s).
self.fields['foo'].queryset = Bar.objects.exclude(id__in=Foo.objects.all().values_list(
'bar_id', flat=True))
PS: Code not tested.
I use abstract models in Django like:
class Tree(models.Model):
parent = models.ForeignKey('self', default=None, null=True, blank=True,
related_name="%(app_label)s_%(class)s_parent")
class Meta:
abstract = True
class Genre(Tree):
title = models.CharField(max_length=150)
And all fields from the abstract model go first in Django's admin panel:
parent:
abstract_field2:
title:
model_field2:
...
Is there a way to put them (fields from abstract classes) in the end of the list?
Or a more general way to define order of fields?
You can order the fields as you wish using the ModelAdmin.fields option.
class GenreAdmin(admin.ModelAdmin):
fields = ('title', 'parent')
Building off rbennell's answer I used a slightly different approach using the new get_fields method introduced in Django 1.7. Here I've overridden it (the code would be in the definition of the parent's ModelAdmin) and removed and re-appended the "parent" field to the end of the list, so it will appear on the bottom of the screen. Using .insert(0,'parent') would put it at the front of the list (which should be obvious if you're familiar with python lists).
def get_fields (self, request, obj=None, **kwargs):
fields = super().get_fields(request, obj, **kwargs)
fields.remove('parent')
fields.append('parent') #can also use insert
return fields
This assumes that your fields are a list, to be honest I'm not sure if that's an okay assumption, but it's worked fine for me so far.
I know it's an old question, but wanted to thow in my two cents, since my use case was exactly like this, but i had lots of models inheriting from one class, so didn't want to write out fields for every admin. Instead I extended the get_form model and rearranged the fields to ensure parent always comes at the end of the fields in the admin panel for add/change view.
class BaseAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(BaseAdmin, self).get_form(request, obj, **kwargs)
parent = form.base_fields.pop('parent')
form.base_fields['parent '] = parent
return form
base_fields is an OrderedDict, so this appends the 'parent' key to the end.
Then, extend this admin for any classes where you want parent to appear at the end:
class GenreAdmin(BaseAdmin):
pass
This simple solution from #acquayefrank (in the comments) worked for me:
The order of fields would depend on the order in which you declare them in your models.
I have a django model as following
class Project(models.Model)
name=models.CharField(max_length=200)
class Application(models.Model)
proj=models.ForeignKey(Project, null=True, blank=True)
I need to modify the admin form of the project to be able to assign multiple applications to the project, so in the admin.py I have created a ModelAdmin class for the project as following
class ProjectAdmin(ModelAdmin)
form=projectForm
project_apps=[]
and the project form as following
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
project_apps =forms.ModelMultipleChoiceField(queryset=Application.objects.all(),required=False,)
def __init__(self, *args, **kwargs):
super(ProjectForm, self).__init__(*args, **kwargs)
if self.instance.id is not None:
selected_items = [ values[0] for values in Application.objects.filter(project=self.instance) ]
self.fields['project_apps'].initial = selected_items
def save(self,commit=True):
super(ProjectForm,self).save(commit)
return self.instance
by doing this I have a multiple select in the create/edit project form.
what I need is to override the save method to save a reference for the project in the selected applications?
how can I get the selected applications ????
Not entirely sure what you're trying to do, but maybe this?
def save(self,commit=True):
kwargs.pop('commit') # We're overriding this with commit = False
super(ProjectForm,self).save(commit)
if self.instance:
for a in self.cleaned_data['project_apps']:
a.proj = self.instance
a.save()
return self.instance
Now, I can't remember if in this case, self.cleaned_data['project_apps'] will actually contain a list of Application objects or not. I suspect it will, but if not this function will take care of that:
def clean_project_apps(self):
app_list = self.cleaned_data['project_apps']
result = []
for a in app_list:
try:
result.append(Application.objects.get(pk=a)
except Application.DoesNotExist:
raise forms.ValidationError("Invalid application record") # to be safe
return result
All in all I think this form is a bad idea though, because basically what is happening here is you're displaying all of the application records which doesn't make sense, since most of them will be associated with other projects.
Oh oh oh!!! Just noticed you wanted this to show up in a Multiple Select list!
You're (probably) doing it wrong
A multiple select means this isn't a one-to-many relationship. It's a many-to-many relationship.
This is what you want to do, easy peasy, doesn't require any custom forms or anything.
class Project(models.Model)
name=models.CharField(max_length=200)
project_apps = models.ManyToMany('Application', null=True, blank=True)
class Application(models.Model)
# nothing here (NO foreign key, you want more than one App/Proj and vice versa)
Indicating that this is a many-to-many field in Project will automagically create the multiple select box in admin. Ta da!
Consider the following situation: -
Suppose my app allows users to create the states / provinces in their
country. Just for clarity, we are considering only ASCII characters
here.
In the US, a user could create the state called "Texas". If this app
is being used internally, let's say the user doesn't care if it is
spelled "texas" or "Texas" or "teXas"
But importantly, the system should prevent creation of "texas" if
"Texas" is already in the database.
If the model is like the following:
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
The uniqueness would be case-sensitive in postgres; that is, postgres
would allow the user to create both "texas" and "Texas" as they are
considered unique.
What can be done in this situation to prevent such behavior. How does
one go about providing case-insenstitive uniqueness with Django and
Postgres
Right now I'm doing the following to prevent creation of case-
insensitive duplicates.
class CreateStateForm(forms.ModelForm):
def clean_name(self):
name = self.cleaned_data['name']
try:
State.objects.get(name__iexact=name)
except ObjectDoesNotExist:
return name
raise forms.ValidationError('State already exists.')
class Meta:
model = State
There are a number of cases where I will have to do this check and I'm not keen on having to write similar iexact checks everywhere.
Just wondering if there is a built-in or
better way? Perhaps db_type would help? Maybe some other solution exists?
You could define a custom model field derived from models.CharField.
This field could check for duplicate values, ignoring the case.
Custom fields documentation is here http://docs.djangoproject.com/en/dev/howto/custom-model-fields/
Look at http://code.djangoproject.com/browser/django/trunk/django/db/models/fields/files.py for an example of how to create a custom field by subclassing an existing field.
You could use the citext module of PostgreSQL https://www.postgresql.org/docs/current/static/citext.html
If you use this module, the the custom field could define "db_type" as CITEXT for PostgreSQL databases.
This would lead to case insensitive comparison for unique values in the custom field.
Alternatively you can change the default Query Set Manager to do case insensitive look-ups on the field. In trying to solve a similar problem I came across:
http://djangosnippets.org/snippets/305/
Code pasted here for convenience:
from django.db.models import Manager
from django.db.models.query import QuerySet
class CaseInsensitiveQuerySet(QuerySet):
def _filter_or_exclude(self, mapper, *args, **kwargs):
# 'name' is a field in your Model whose lookups you want case-insensitive by default
if 'name' in kwargs:
kwargs['name__iexact'] = kwargs['name']
del kwargs['name']
return super(CaseInsensitiveQuerySet, self)._filter_or_exclude(mapper, *args, **kwargs)
# custom manager that overrides the initial query set
class TagManager(Manager):
def get_query_set(self):
return CaseInsensitiveQuerySet(self.model)
# and the model itself
class Tag(models.Model):
name = models.CharField(maxlength=50, unique=True, db_index=True)
objects = TagManager()
def __str__(self):
return self.name
a very simple solution:
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def clean(self):
self.name = self.name.capitalize()
Explicit steps for Mayuresh's answer:
in postgres do: CREATE EXTENSION citext;
in your models.py add:
from django.db.models import fields
class CaseInsensitiveTextField(fields.TextField):
def db_type(self, connection):
return "citext"
reference: https://github.com/zacharyvoase/django-postgres/blob/master/django_postgres/citext.py
in your model use: name = CaseInsensitiveTextField(unique=True)
On the Postgres side of things, a functional unique index will let you enforce unique values without case. citext is also noted, but this will work with older versions of PostgreSQL and is a useful technique in general.
Example:
# create table foo(bar text);
CREATE TABLE
# create unique index foo_bar on foo(lower(bar));
CREATE INDEX
# insert into foo values ('Texas');
INSERT 0 1
# insert into foo values ('texas');
ERROR: duplicate key value violates unique constraint "foo_bar"
Besides already mentioned option to override save, you can simply store all text in lower case in database and capitalize them on displaying.
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def save(self, force_insert=False, force_update=False):
self.name = self.name.lower()
super(State, self).save(force_insert, force_update)
You can use lookup='iexact' in UniqueValidator on serializer, like this:
class StateSerializer(serializers.ModelSerializer):
name = serializers.CharField(validators=[
UniqueValidator(
queryset=models.State.objects.all(),lookup='iexact'
)]
django version: 1.11.6
If you don't want to use a postgres-specific solution, you can create a unique index on the field with upper() to enforce uniqueness at the database level, then create a custom Field mixin that overrides get_lookup() to convert case-sensitive lookups to their case-insensitive versions. The mixin looks like this:
class CaseInsensitiveFieldMixin:
"""
Field mixin that uses case-insensitive lookup alternatives if they exist.
"""
LOOKUP_CONVERSIONS = {
'exact': 'iexact',
'contains': 'icontains',
'startswith': 'istartswith',
'endswith': 'iendswith',
'regex': 'iregex',
}
def get_lookup(self, lookup_name):
converted = self.LOOKUP_CONVERSIONS.get(lookup_name, lookup_name)
return super().get_lookup(converted)
And you use it like this:
from django.db import models
class CICharField(CaseInsensitiveFieldMixin, models.CharField):
pass
class CIEmailField(CaseInsensitiveFieldMixin, models.EmailField):
pass
class TestModel(models.Model):
name = CICharField(unique=True, max_length=20)
email = CIEmailField(unique=True)
You can read more about this approach here.
You can do this by overwriting the Model's save method - see the docs. You'd basically do something like:
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def save(self, force_insert=False, force_update=False):
if State.objects.get(name__iexact = self.name):
return
else:
super(State, self).save(force_insert, force_update)
Also, I may be wrong about this, but the upcoming model-validation SoC branch will allow us to do this more easily.
Solution from suhail worked for me without the need to enable citext, pretty easy solution only a clean function and instead of capitalize I used upper(). Mayuresh's solution also works but changed the field from CharField to TextField.
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def clean(self):
self.name = self.name.upper()