Django admin saves a copy of the object instead of overwrite - python

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

Django Model is creating another model after editing it [duplicate]

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.

How to override M2M field names and models in Django with an existing database?

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.

Django save and return object at once (with a custom pk)

I am creating the object of a model using custom create method create_actor like this:
class ActorsManager(models.QuerySet):
def create_actor(self, email,
actortype,
locationid,
primaryphone, actoruniversalid):
actor = self.model(email=email,
actortype=actortype,
locationid = locationid,
primaryphone=primaryphone, actoruniversalid= actoruniversalid)
actor.save(using='gpr')
return actor
actor_entry = Actors.objects.using('gpr').create_actor(email='', actortype=1, locationid = location_entry,primaryphone='', actoruniversalid= new_bluenumber)
I am not getting the recently created object in actor_entry variable,
may be i am doing something wrong, please help.
I'm using a before insert SQL trigger to generate uuid as pk(CharField here) in the database, so the object does have a pk (generated by the database) before its saved.
Actors Model
class Actors(models.Model):
actorid = models.CharField(db_column='ActorID', primary_key=True, max_length=255) # Field name made lowercase.
actoruniversalid = models.CharField(db_column='ActorBluenumber', unique=True, blank=True, null=True, max_length=254)
......
......
objects = ActorsManager.as_manager()
class Meta:
managed = False
db_table = 'actors'
Unless you created your actors table by hand, the ActorID will not automatically be set. Depending on your database backend, None may be treated the same as the empty string, and as such could be a valid primary key. I believe Django takes special measures to update the row if one exists with the same primary key unless you specify forceinsert=True when invoking save()
As #knbk pointed out that only an AutoField can set the pk to an object and so the pythonic logic to retrieve the pk is not executed when its not an AutoField and so django doesn't know about it.
After searching the django documentation I found an alternative to AutoField for UUIDs,an UUIDField which is best suited for my Use Case.
actorid = models.UUIDField(db_column='ActorID', primary_key=True, editable=False, default=uuid.uuid4)
So now the django does know of the pk and thus the object is retrieved after being saved.
Thank you all for the help.

Django: allow user to add fields to model

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.

Why doesn't self.object in a CreateView have an id after saving to the database?

Following the comment on the accepted answer on django createview how to get the object that is created, I am attempting to use the id from a user created by a CreateView in its get_success_url method. However, even though it is definitely being saved to MySQL and receiving an id, when I access self.object, it doesn't have an id to use. The model does have an id property. Why wouldn't I be able to access the id? If I'm being led astray by the linked comment, what is the right way to get the id?
Reference code:
models.py
from django.db import models
class User(models.Model):
id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=128)
active = models.BooleanField(blank=True)
created_date = models.DateTimeField()
class Meta:
managed = False
db_table = 'user'
def __unicode__(self):
return self.name
views.py
from django.views.generic import CreateView
from django.core.urlresolvers import reverse
from forms import AddUserForm
class AddUserView(CreateView):
form_class = AddUserForm
fields = ['name', 'active'] # id and created_date are auto-filled upon save
template_name = 'webApp/add_user_form.html'
def get_success_url(self):
print self.object # Prints the name of the submitted user
print self.object.id # Prints None
return reverse("webApp:user:stepTwo", args=(self.object.id,))
It's blank because the object is not actually receiving an id. That's because the id field should be an AutoField, not IntegerField, so that it is declared as auto-incrementing.
Actually though you should not declare it at all: an primary key AutoField named id is the default, so you should leave it out.
Note you'll need to migrate your database table, or drop and recreate it, after you make this change.

Categories