I have a model with OneToOneFiled named alg_id, and when I go to admin panel and change this filed in existing object, then a new object is created, but I'd like to overwrite same object with different alg_id.
When I change other simple text fileds - all works fine.
If I change alg_id to one, that already exists in Database, then that object gets overwritten - will be better if I'll get a warning here..
How could I achieve that?
ps. this project use 2.2.6 Django version
models.py :
from django.db import models
from django.utils.translation import gettext_lazy as _
class AchievementInfo(models.Model):
GROUPS = (
('Results', 'Results'),
('Service', 'Service'),
('Team', 'Team'),
('Development', 'Development')
)
algorithm_id = models.OneToOneField(
'core.Algorithms',
models.DO_NOTHING,
verbose_name=_('Alg id'),
primary_key=True,
)
role_key = models.CharField(_('Role'), max_length=25)
group = models.CharField(_('Group'), max_length=50, choices=GROUPS)
name = models.CharField(_('Name'), max_length=100)
web_name = models.CharField(_('Shown name'), max_length=75)
class Meta:
db_table = 'achievements_guide'
default_permissions = ()
ordering = ('web_name', 'role_key', 'algorithm_id')
verbose_name = _('Achievement')
verbose_name_plural = _('Achievements')
def __str__(self):
return f'{self.algorithm_id}: {self.name}, {self.web_name}, {self.role_key}'
admin.py:
from django.contrib import admin
from achievements.models import AchievementInfo
#admin.register(AchievementInfo)
class AchiAdmin(admin.ModelAdmin):
list_display = ("web_name", "group", "role_key", "algorithm_id")
list_filter = ("web_name", "role_key")
raw_id_fields = ("algorithm_id", )
Django tracks the object you're trying to edit or add only by a primary_key. If the newly set primary key matches some object in the database, this object will be replaced when saving the newly created object. Similarly, if you change your primary key and save the object, Django will not update the old one, but create the new one (or replace newly matching one, if applicable) instead.
As you are using your OneToOneField as a primary key for your object, what you want to achieve is not possible without a complex, additional logic when saving your object.
Instead, you can just create additional field as your primary key (or let Django do it for you by not using primary_key=True in any of the fields in your model), so Django can properly track by itself which object are you editing.
Related
I have a model with OneToOneFiled named alg_id, and when I go to admin panel and change this filed in existing object, then a new object is created, but I'd like to overwrite same object with different alg_id.
When I change other simple text fileds - all works fine.
If I change alg_id to one, that already exists in Database, then that object gets overwritten - will be better if I'll get a warning here..
How could I achieve that?
ps. this project use 2.2.6 Django version
models.py :
from django.db import models
from django.utils.translation import gettext_lazy as _
class AchievementInfo(models.Model):
GROUPS = (
('Results', 'Results'),
('Service', 'Service'),
('Team', 'Team'),
('Development', 'Development')
)
algorithm_id = models.OneToOneField(
'core.Algorithms',
models.DO_NOTHING,
verbose_name=_('Alg id'),
primary_key=True,
)
role_key = models.CharField(_('Role'), max_length=25)
group = models.CharField(_('Group'), max_length=50, choices=GROUPS)
name = models.CharField(_('Name'), max_length=100)
web_name = models.CharField(_('Shown name'), max_length=75)
class Meta:
db_table = 'achievements_guide'
default_permissions = ()
ordering = ('web_name', 'role_key', 'algorithm_id')
verbose_name = _('Achievement')
verbose_name_plural = _('Achievements')
def __str__(self):
return f'{self.algorithm_id}: {self.name}, {self.web_name}, {self.role_key}'
admin.py:
from django.contrib import admin
from achievements.models import AchievementInfo
#admin.register(AchievementInfo)
class AchiAdmin(admin.ModelAdmin):
list_display = ("web_name", "group", "role_key", "algorithm_id")
list_filter = ("web_name", "role_key")
raw_id_fields = ("algorithm_id", )
Django tracks the object you're trying to edit or add only by a primary_key. If the newly set primary key matches some object in the database, this object will be replaced when saving the newly created object. Similarly, if you change your primary key and save the object, Django will not update the old one, but create the new one (or replace newly matching one, if applicable) instead.
As you are using your OneToOneField as a primary key for your object, what you want to achieve is not possible without a complex, additional logic when saving your object.
Instead, you can just create additional field as your primary key (or let Django do it for you by not using primary_key=True in any of the fields in your model), so Django can properly track by itself which object are you editing.
I'm using Django 3.1.3 and working with an existing postgresql database. Most of the models and fields names of this DB are badly chosen and/or way too long. Most of the time its easy to change them with some handy Django options like so :
class NewModelName(models.Models):
new_field_name = models.CharField(max_length=50, db_column='old_field_name')
class Meta:
managed=False
db_table='database_old_table_name'
But let say I want to change a M2M field name and the corresponding model name. I'd like to have something like :
class Foo(models.Models):
new_m2m_field_name = models.ManyToManyField('RelatedModel', blank=True, db_column='old_m2m_field_name')
class Meta:
managed=False
db_table='foo_old_table_name'
class RelatedModel(models.Models):
name = models.CharField(max_length=50)
class Meta:
managed=False
db_table='related_model_old_table_name'
But if I do that, Django will throw an error stating
django.db.utils.ProgrammingError: relation "foo_new_m2m_field_name" does not exist. It is like it is ignoring the db_column option. Any idea how I could get to a similar result ?
Thanks!
From Django documentation regarding ManyToManyField
ManyToManyField.db_table The name of the table to create for storing
the many-to-many data. If this is not provided, Django will assume a
default name based upon the names of: the table for the model defining
the relationship and the name of the field itself.
Also depending on column names (non standard names) in original database you might have to define through model ( pivot table) as through table
You will probably need to manually define the Through model (that Django would otherwise implicitly create behind the scenes) in order to make it unmanaged.
class Foo(models.Models):
new_m2m_field_name = models.ManyToManyField(
"RelatedModel",
blank=True,
db_column="old_m2m_field_name",
through="FooRelatedJoin", # <- new
)
class Meta:
managed = False
db_table = "foo_old_table_name"
class RelatedModel(models.Models):
name = models.CharField(max_length=50)
class Meta:
managed = False
db_table = "related_model_old_table_name"
class FooRelatedJoin(models.Models): # <- all new
foo = models.ForeignKey(Foo)
related_model = models.ForeignKey(RelatedModel)
class Meta:
managed = False
db_table = "foo_join_table"
You could add a property db_table (link)
linking to the previous table, named foo_old_table_name in your case.
According to the doc,
By default, this table name is generated using the name of the
many-to-many field and the name of the table for the model that
contains it
So for the field new_m2m_field_name, the previous table making the link was named : old_field_name_database_old_table_name.
Hence :
new_field_name = models.CharField(max_length=50, db_column='old_field_name', db_table='old_field_name_database_old_table_name')
The option through could be changed too, but I do not think it is necessary if the modifications on names are coherent.
I am using django-cms
many to many field is working fine when i add plugins or update but
after publish page i didn't get any data where i add many to many field.
models are look like this
#python_2_unicode_compatible
class ClientLogo(CMSPlugin):
client_logo = models.ManyToManyField(LogoPluginModel, blank=True)
class LogoPluginModel(CMSPlugin):
title = models.CharField(max_length=100)
here field 'client_logo' will be disapper when i publish djagno-cms page
The relations need to be explicitly copied through the copy_relations method (see django-cms models manual). It's also advantageous to add a related_name property to the ManyToManyField, especially when a model contains multiple sets of foreign keys or ManyToManyFields.
#python_2_unicode_compatible
class ClientLogo(CMSPlugin):
client_logo = models.ManyToManyField(
LogoPluginModel,
blank=True,
related_name='client_logos',
)
class LogoPluginModel(CMSPlugin):
title = models.CharField(max_length=100)
def copy_relations(self, oldinstance):
self.client_logos.all().delete()
for logo in oldinstance.client_logos.all():
logo.pk = None
logo.showroom = self
logo.save()
I am just starting with Django and want to create a model for an application.
I find Djangos feature to
- automatically define validations and html widget types for forms according to the field type defined in the model and
- define a choice set for the field right in the model
very usefull and I want to make best use of it. Also, I want to make best use of the admin interface.
However, what if I want to allow the user of the application to add fields to the model? For example, consider a simple adress book. I want the user to be able to define additional atributes for all of his contacts in the admin settings, i.e. add a fax number field, so that a fax number can be added to all contacts.
from a relational DB perspective, I would have a table with atributes (PK: atr_ID, atr_name, atr_type) and an N:N relation between atributes and contacts with foreign keys from atributes and contacts - i.e. it would result in 3 tables in the DB. right?
but that way I cannot define the field types directly in the Django model. Now what is best practice here? How can I make use of Djangos functionality AND allow the user to add aditional/custom fields via the admin interface?
Thank you! :)
Best
Teconomix
i would suggest storing json as a string in the database, that way it can be as extendable as you want and the field list can go very long.
Edit:
If you are using other damn backends you can use Django-jsonfield. If you are using Postgres then it has a native jsonfield support for enhanced querying, etc.
Edit 2:
Using django mongodb connector can also help.
I've used this approach, first seen in django-payslip, to allow for extendable fields. This provides a structure for adding fields to models, from which you can allow users to add/edit through standard view procedures (no admin hacking necessary). This should be enough to get you started, and taking a look at django-payslip's source code (see the views) also provides view Mixins and forms as an example of how to render to users.
class YourModel(models.Model):
extra_fields = models.ManyToManyField(
'your_app.ExtraField',
verbose_name=_('Extra fields'),
blank=True, null=True,
)
class ExtraFieldType(models.Model):
"""
Model to create custom information holders.
:name: Name of the attribute.
:description: Description of the attribute.
:model: Can be set in order to allow the use of only one model.
:fixed_values: Can transform related exta fields into choices.
"""
name = models.CharField(
max_length=100,
verbose_name=_('Name'),
)
description = models.CharField(
max_length=100,
blank=True, null=True,
verbose_name=_('Description'),
)
model = models.CharField(
max_length=10,
choices=(
('YourModel', 'YourModel'),
('AnotherModel', 'AnotherModel'), # which models do you want to add extra fields to?
),
verbose_name=_('Model'),
blank=True, null=True,
)
fixed_values = models.BooleanField(
default=False,
verbose_name=_('Fixed values'),
)
class Meta:
ordering = ['name', ]
def __unicode__(self):
return '{0}'.format(self.name)
class ExtraField(models.Model):
"""
Model to create custom fields.
:field_type: Connection to the field type.
:value: Current value of this extra field.
"""
field_type = models.ForeignKey(
'your_app.ExtraFieldType',
verbose_name=_('Field type'),
related_name='extra_fields',
help_text=_('Only field types with fixed values can be chosen to add'
' global values.'),
)
value = models.CharField(
max_length=200,
verbose_name=_('Value'),
)
class Meta:
ordering = ['field_type__name', ]
def __unicode__(self):
return '{0} ({1}) - {2}'.format(
self.field_type, self.field_type.get_model_display() or 'general',
self.value)
You can use InlineModelAdmin objects. It should be something like:
#models.py
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=100)
class ContactType(models.Model):
name = models.CharField(max_length=100)
class Contact(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
contact_type = models.ForeignKey(ContactType, on_delete=models.CASCADE)
value = models.CharField(max_length=100)
#admin.py
from django.contrib import admin
class ContactInline(admin.TabularInline):
model = Contact
class PersonAdmin(admin.ModelAdmin):
inlines = [
ContactInline,
]
By the way... stackoverflow questions should contain some code. You should try to do something before asking a question.
I'm not sure that my title really made sense. Basically (from the code below), when I access the admin screen, I want a project to display with its client and a client to display all attached projects. Is there any way to do this?
class Client(models.Model):
title = models.CharField(max_length=250, null=True)
#project = models.ManyToManyField(Project)
#status = models.CharField(max_length=250)
class Project(models.Model):
project_choices = (
('L1', 'Lead'),
('C1', 'Confirmed'),
('P1', 'In Progress'),
('P1', 'Paid'),
)
title = models.CharField(verbose_name='Project Title', max_length=250, null=True)
client = models.ForeignKey(Client)
project_status = models.CharField(max_length=2,
choices=project_choices,
default='P1')
def __unicode__(self):
return self.title
I would suggest setting up a custom ModelAdmin and using the list_display to indicate which fields you want to show in the admin. It is fairly customizable, and you can add callables that could display exactly the info you indicate. An example ModelAdmin for the Client Model is below.
# project/app/admin.py
# Callable to add to ModelAdmin List Display
def show_client_projects(obj):
project_list = [p.title for p in obj.project_set.all()]
return ', '.join(project_list)
show_client_projects.short_description = 'Client Projects'
# Custom ModelAdmin
class ClientAdmin(admin.ModelAdmin):
list_display = ('title', 'show_client_projects')
You will need to create ModelAdmin classes for your models to define columns to display in the built-in Django admin:
https://docs.djangoproject.com/en/dev/ref/contrib/admin/
This is particularly relevant:
ManyToManyField fields aren’t supported, because that would entail executing a separate SQL statement for each row in the table. If you want to do this nonetheless, give your model a custom method, and add that method’s name to list_display. (See below for more on custom methods in list_display.)
So, you can create a method for your Client which loads up the Client's projects, and include that in the list_display.
Something like this should put you on the right track:
# In your models.py...
from django.contrib import admin
class Client(models.Model):
title = models.CharField(max_length=250, null=True)
def projects(self):
return Project.objects.filter(client=self)
class ClientAdmin(models.ModelAdmin):
list_display = ('title','projects',)
admin.site.register(Client,ClientAdmin)