am working on a concept in which I want to capture certain information when a model is saved. To understand the full picture, I have a app core with the following model
core/models.py
from django.db import models
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from transmeta import TransMeta
from django.utils.translation import ugettext_lazy as _
import signals
class Audit(models.Model):
## TODO: Document
# Polymorphic model using generic relation through DJANGO content type
operation = models.CharField(_('Operation'), max_length=40)
operation_at = models.DateTimeField(_('Operation At'), auto_now_add=True)
operation_by = models.ForeignKey(User, null=True, blank=True, related_name="%(app_label)s_%(class)s_y+")
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
Audit model is a generic content type, am currently attaching it with other apps such as in blog
blog/models.py
from django.db import models
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.template.defaultfilters import slugify
from django.utils.translation import ugettext_lazy as _
# Create your models here.
class Meta:
verbose_name_plural = "Categories"
class article(models.Model):
title = models.CharField(max_length=100)
slug = models.SlugField(editable=False, unique_for_year=True)
content = models.TextField()
is_active = models.BooleanField()
published_at = models.DateTimeField('Publish at',auto_now=True)
related_articles = models.ManyToManyField('self', null=True, blank=True)
audit_obj = generic.GenericRelation('core.Audit', editable=False, null=True, blank=True)
My first attempt was, I made a post_save signal in which I was checking if the instance passed containing audit_obj attribute and then saving a record in using article.audit_obj.create().save().
Unfortunately, this did not entirely work out for me since I cannot pass the request nor I can access the request to retrieve the user information.
So, I was thinking to create a custom signal and override the form_save method (if there is such a thing) and then using arguments to pass the request object as well as the model object.
Any advice on how I can do that?
Regards,
EDIT (20th of Jan, 2011):
Thanks #Yuji for your time. Well, what am trying to achieve is to keep my code as DRY as possible. What I want to do ultimately, every time I create new model, I will only create an additional attribute and name it audit_obj and I will create a single piece of code, either a signal or to override the save method inside the django core itself. The peiece of code will always check if an attribute with the following name exists and therefore creates a record in aduti table.
I'd just create a function in my model class or Manager and call it from my form save (wherever yours might be)
class AuditManager(models.Manager):
def save_from_object(self, request, obj):
audit = Audit()
audit.object_id = obj.id
audit.operation_by = request.user
# ...
audit.save()
class Audit(models.Model):
## TODO: Document
# Polymorphic model using generic relation through DJANGO content type
operation = models.CharField(_('Operation'), max_length=40)
operation_at = models.DateTimeField(_('Operation At'), auto_now_add=True)
operation_by = models.ForeignKey(User, null=True, blank=True, related_name="%(app_label)s_%(class)s_y+")
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
objects = AuditManager()
class MyBlogForm(forms.ModelForm):
class Meta:
model = article # btw I'd use Capital Letters For Classes
def save(self, *args, **kwargs):
super(MyBlogForm, self).save(*args, **kwargs)
Audit.objects.save_from_object(request, self.instance)
Related
I have a model with an attribute that is connected to another model as follow:
class Book(models.Model):
synced = models.OneToOneField('SyncedBook'
related_name='internal',
on_delete=models.CASCADE)
# some more attributes here...
#property
def book_address(self)
return self.synced.book_address
However, the book_address is a also a FK in the SyncedBook table as follow:
book_address = models.ForeignKey('Address', db_index=True, null=True, blank=True,
related_name='address_book', on_delete=models.PROTECT)
I don't know and understand how to be able to edit the book_address through the Django admin page in class BookingAdmin(admin.ModelAdmin), even though I have read over the documentation. At first I have the attribute as readonly, but now I want to be able to edit it and save the new address from the Address table. Is there a way to make it happen through the class BookingAdmin(admin.ModelAdmin) and how? Any example and solution would be appreciate
Model properties are typically used for presenting logically defined data for a particular model instance and not necessarily storing data on the model instance itself.
An example of when to use a model property is as follows:
# Defines a product instance
class Product(model.Models):
name = models.CharField(max_length=100)
description = models.TextField()
active = models.BooleanField(default=True)
cost = models.DecimalField(max_digits=5, decimal_places=2)
price = models.DecimalField(max_digits=5, decimal_places=2)
# calculate profits on product
#property
def profit(self)
p = self.price - self.cost
return p
In your case, you are trying to actually be able to modify data against a related model instance within the django admin. To me this sounds like more specifically an Inline (click here for documentation)
So in your case, you would need to create something like the following to your admin.py file:
class SyncedBookInline(admin.TabularInline):
model = BookInline
#admin.Register(Book)
class BookAdmin(admin.ModelAdmin):
# all your model admin settings
inlines = [SyncedBookInline]
Additional Info:
The Inline solution should still work for you. Please see the working code listed below:
models.py:
from django.db import models
class Hero(models.Model):
name = models.CharField(max_length=50)
class HeroAcquaintance(models.Model):
name = models.CharField(max_length=50)
hero = models.OneToOneField(Hero, on_delete=models.CASCADE)
admin.py:
from django.contrib import admin
from .models import *
class HeroAcquaintanceInline(admin.TabularInline):
model = HeroAcquaintance
#admin.register(Hero)
class HeroAdmin(admin.ModelAdmin):
list_display = (
'name',
)
inlines = [HeroAcquaintanceInline]
#admin.register(HeroAcquaintance)
class HeroAcquaintanceAdmin(admin.ModelAdmin):
list_display = (
'name',
)
Screenshot:
I want to change the model used for the default LogEntry class so it creates a varchar rather than a clob in the database for the attribute "object_id"
The original model is defined in
django/contrib/admin/models.py
class LogEntry(models.Model):
action_time = models.DateTimeField(_('action time'), auto_now=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
content_type = models.ForeignKey(ContentType, blank=True, null=True)
object_id = models.TextField(_('object id'), blank=True, null=True)
object_repr = models.CharField(_('object repr'), max_length=200)
action_flag = models.PositiveSmallIntegerField(_('action flag'))
change_message = models.TextField(_('change message'), blank=True)
I want to change the definition of object_id to
object_id = models.Charfield(_('object id'), max_length=1000, blank=True, null=True)
I'm aware that this could cause issues if someone defined a primary key on an entity which is larger than a varchar(1000), but I wouldn't want an entity with a PK defined like that so I'm happy with the limitation.
This will greatly improve the efficiency of the queries when accessing the history log.
I don't really want to hack the actual model definition, but I can't find out how to elegantly override the model definition.
Any ideas?
Django’s model fields provide an undocumented contribute_to_class method.
The other feature of Django we can use is the class_prepared signal.
from django.db.models import CharField
from django.db.models.signals import class_prepared
def add_field(sender, **kwargs):
"""
class_prepared signal handler that checks for the model named
MyModel as the sender, and adds a CharField
to it.
"""
if sender.__name__ == "MyModel":
field = CharField("New field", max_length=100)
field.contribute_to_class(sender, "new_field")
class_prepared.connect(add_field)
To override field you can simply delete original field from model:
from django.db.models import CharField
from django.db.models.signals import class_prepared
def override_field(sender, **kwargs):
if sender.__name__ == "LogEntry":
field = CharField('object id', max_length=1000, blank=True, null=True)
sender._meta.local_fields = [f for f in sender._meta.fields if f.name != "object_id"]
field.contribute_to_class(sender, "object_id")
class_prepared.connect(override_field)
I have just tested this solution by placing this code in __init__.py of my app. You will also need to write a custom migration:
from django.db import migrations, models
class Migration(migrations.Migration):
def __init__(self, name, app_label):
# overriding application operated upon
super(Migration, self).__init__(name, 'admin')
dependencies = [
('my_app', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='logentry',
name='object_id',
field=models.CharField('object id', max_length=1000, blank=True, null=True),
),
]
Looks like it works but use it on your own risk.
You can read more here.
So, I have this these model:
class Media(models.Model):
title = models.CharField(max_length=50, blank=True)
slug = models.SlugField(max_length=70, blank=True, editable=False)
class Meta:
abstract = True
class Photo(Media):
source = models.ImageField(upload_to='uploads/gallery/photo')
class Video(Media):
source = models.FileField(upload_to='uploads/gallery/photo')
class Item(models.Model):
content_type = models.ForeignKey(
ContentType,
limit_choices_to={'model__in': ['photo', ]},
on_delete=models.CASCADE,
)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
class Album(Media):
items = models.ManyToManyField(Item)
How can I have an album admin with photo and video as inline so i can upload photo and video when i created an album ?
When I tried making inline for photo and hook it to album admin, I get error "photo has no foreignkey to album", pretty obvious, from there I think there should be a way to link foreignkey needed by album admin with content object from model item.
Note: I specifically doesn't want an item admin. An item are created at model post save signals.
I do not think this works out of the box. But you can use libraries like eg django-smart-selects-generics. Depending on your django version you might need to update some files there.
Installation works with:
pip install django-smart-selects-generic
You also need to have django-smart-selects installed.
Then add both apps in the settings.
INSTALLED_APPS = (
...
'smart_selects',
'smart_generic'
)
And then in your admin.py you can do:
from smart_generic.form_fields import GenericChainedModelChoiceField
from django.forms.models import ModelForm
class TForm(ModelForm):
object_id = GenericChainedModelChoiceField('content_type','content_type',label=u'Content object',queryset=TargetRelation.objects.all())
class Meta:
model = TargetRelation
fields = ['target','content_type']
class TRAdmin(admin.ModelAdmin):
form = TForm
class TRInline(admin.TabularInline):
model = TargetRelation
form = TForm
class PlanetAdmin(admin.ModelAdmin):
inlines=[TRInline,]
Depending on your django version you might need to replace in widgets.py:
Media = ChainedSelect.Media
by
.media = ChainedSelect.Media
And in smart_select views.py add:
import json as simplejson
and replace the return statement by:
return HttpResponse(json, content_type='application/json')
I'm having trouble overriding the formset on a TabularInline inline of a ModelAdmin object in my admin site. I know you're supposed to have a model associated with a TabularInline object, but I'm not sure how to specify this on the form object used to generate the formset. With the code below, I'm getting "'AppAssetInline.formset' does not inherit from BaseModelFormSet."
class AppAssetForm(forms.ModelForm):
model = App.assets.through
primary = forms.BooleanField()
uuid = forms.CharField()
class AppAssetInline(admin.TabularInline):
model = App.assets.through
AssetFormset = formset_factory(AppAssetForm)
formset = AssetFormset
class AppAdmin(admin.ModelAdmin):
inlines = [AppAssetInline,]
The answer to my question didn't have to do with how I was structuring my forms, but rather how I was joining fields on my models. I had the following structure in my models:
class App(models.Model):
package = models.FileField(upload_to=settings.APP_PACKAGE_ROOT)
assets = models.ManyToManyField('AppAsset', blank=True, null=True)
download_count = models.IntegerField(default=0)
class AppAsset(models.Model):
def __unicode__(self):
return self.asset_file.name
notes = models.CharField(max_length=255, null=True, blank=True)
type = models.CharField(max_length=255, null=True, blank=True)
asset_file = models.FileField(upload_to=settings.APP_PACKAGE_ROOT)
What I did was change the structure such that AppAsset now has a foreign key on App for its assets. After that, I could use the TabularInline on the AppAsset model with no problems. Here are the latest source files:
https://github.com/ridecharge/spout/blob/master/Spout/AppDistribution/models.py
https://github.com/ridecharge/spout/blob/master/Spout/AppDistribution/admin.py
You should use django.forms.models.inlineformset_factory instead of formset_factory
Am working on an project in which I made an app "core" it will contain some of the reused models across my projects, most of those are polymorphic models (Generic content types) and will be linked to different models.
Example below am trying to create audit model and will be linked to several models which may require auditing.
This is the polls/models.py
from django.db import models
from django.contrib.auth.models import User
from core.models import *
from django.contrib.contenttypes import generic
class Poll(models.Model):
## TODO: Document
question = models.CharField(max_length=300)
question_slug=models.SlugField(editable=False)
start_poll_at = models.DateTimeField(null=True)
end_poll_at = models.DateTimeField(null=True)
is_active = models.BooleanField(default=True)
audit_obj=generic.GenericRelation(Audit)
def __unicode__(self):
return self.question
class Choice(models.Model):
## TODO: Document
choice = models.CharField(max_length=200)
poll=models.ForeignKey(Poll)
audit_obj=generic.GenericRelation(Audit)
class Vote(models.Model):
## TODO: Document
choice=models.ForeignKey(Choice)
Ip_Address=models.IPAddressField(editable=False)
vote_at=models.DateTimeField("Vote at", editable=False)
here is the core/modes.py
from django.db import models
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
class Audit(models.Model):
## TODO: Document
# Polymorphic model using generic relation through DJANGO content type
created_at = models.DateTimeField("Created at", auto_now_add=True)
created_by = models.ForeignKey(User, db_column="created_by", related_name="%(app_label)s_%(class)s_y+")
updated_at = models.DateTimeField("Updated at", auto_now=True)
updated_by = models.ForeignKey(User, db_column="updated_by", null=True, blank=True,
related_name="%(app_label)s_%(class)s_y+")
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField(unique=True)
content_object = generic.GenericForeignKey('content_type', 'object_id')
and here is polls/admin.py
from django.core.context_processors import request
from polls.models import Poll, Choice
from core.models import *
from django.contrib import admin
class ChoiceInline(admin.StackedInline):
model = Choice
extra = 3
class PollAdmin(admin.ModelAdmin):
inlines = [ChoiceInline]
admin.site.register(Poll, PollAdmin)
Am quite new to django, what am trying to do here, insert a record in audit when a record is inserted in polls and then update that same record when a record is updated in polls.
You can override the save() method of Poll model or you can use Django Signals
to do it.
try something like:
def save(self, *args, **kwargs,request):
super(ModelName, self).save(*args, **kwargs)
## followed by your code
you can save your model instance using
modelinstance.save(request)