Temporary models in django - django

In one celery task I need to create temporary table in database. In this article Daniel Roseman explained how to create one. But this solution does not work in Django 1.9. I tried to look into Django docs and Google but I was unable to find anything useful.
Code from mentioned article which worked in Django 1.8:
from django.db import models, cursor
from django.contrib.contenttypes.management import update_contenttypes
from django.core.management import call_command
class TempCustomerAddress(models.Model):
address = models.ForeignKey('accounts.Address')
legacy_id = models.CharField(max_length=12, unique=True)
class Meta:
app_label = 'utils'
class Command(NoArgsCommand):
def handle_noargs(self, **options):
models.register_models('utils', TempCustomerAddress)
models.signals.post_syncdb.disconnect(update_contenttypes)
call_command('syncdb')
# ... do importing and stuff referring to TempCustomerAddress ...
cursor = connection.cursor()
cursor.execute('DROP TABLE `utils_tempcustomeraddress`')

In django 1.9 you actually don't need to register anything. You just create model the same way as in models.py and that's it. You only need to make sure that it is not in models.py file because than it will be permanent model.
This example assumes that you already ran all migrations.
from django.db import models, cursor
from django.contrib.contenttypes.management import update_contenttypes
from django.core.management import call_command
class TempCustomerAddress(models.Model):
address = models.ForeignKey('accounts.Address')
legacy_id = models.CharField(max_length=12, unique=True)
class Meta:
app_label = 'utils'
class Command(NoArgsCommand):
def handle_noargs(self, **options):
with connection.cursor() as cursor:
cursor.execute('DROP TABLE IF EXISTS utils_tempcustomeraddress')
cursor.execute('''
CREATE TABLE utils_tempcustomeraddress (
id INTEGER PRIMARY KEY NOT NULL,
address_id REFERENCES accounts_address (id),
legacy_id VARCHAR(12) UNIQUE
);
'''
# ... do importing and stuff referring to TempCustomerAddress ...
cursor.execute('DROP TABLE `utils_tempcustomeraddress`')

I needed to create a temporary model derived from a "permanent" model, and use temporary table storage to avoid polluting the tables of the permanent one. After a lot of poking around including an article relating to Django 0.96, some newer material it points to for Django 1.2 and some old material based on migration tech integrated into Django I finally came up with a recipe that works with Django 2.0.
First, I needed to explicitly specify the database table name using the Meta:
model_name = re.sub('[#.]', '_', 'some_string')
class Meta:
app_label = original_model._meta.app_label
#
# Use the explicit name for the database table.
#
db_table = '"' + model_name.lower() + '"'
Then I created the Model class by copying what I needed from the original:
attr = {'__module__': __name__, 'Meta': Meta}
local_fields = [field.name for field in original_model._meta.local_fields]
for field in original_model._meta.fields:
#
# Clone only the fields which we need, not forgetting that we only
# want one primary key.
#
clone = field.clone()
if field.name in local_fields:
local_fields.remove(field.name)
else:
clone.primary_key = False
if not isinstance(field, (db_models.AutoField, db_models.OneToOneField, db_models.ManyToManyField)):
attr[field.name] = clone
new_model = type(model_name, (db_models.Model,), attr)
The hard part was tracking down how to create the new table for the model. Once found, the answer was simple:
from django.db import connection
with connection.schema_editor() as schema_editor:
schema_editor.create_model(new_model)

Related

Save django CreateView in Python Shell

I'm trying to write a test for my CreateView view in django. As part fo the test, I want to create a new object through a CreateView class, though I'm unsure how to save the object through tests.py.
models.py
class MyModel(models.Model):
name = models.CharField(
max_length = 50,
)
views.py
class MyCreateView(CreateView):
model = MyModel
tests.py
from myapp.views import MyCreateView
m = MyCreateView()
m.name = 'John Doe'
# save object here
Neither m.save() nor m.submit() will work. Any suggestions?
Please refer to Django docs: https://docs.djangoproject.com/en/4.0/topics/testing/
It is well documented and shows how to test views and models.

New attributes are not showing up in django admin dashboard

Previously I was using my project with sqlite. Then started a new project copied the data from previous project and made some changes, and I'm using this with mysql.
This is my models.py(not full)
from django.db import models
from django.db.models import CheckConstraint, Q, F
class College(models.Model):
CITY_CHOICES=[('BAN','Bangalore')]
id=models.IntegerField(primary_key=True)
name=models.CharField(max_length=50)
city=models.CharField(choices=CITY_CHOICES,default='BAN',max_length=10)
fest_nos=models.IntegerField()
image=models.ImageField(default='default.jpg',upload_to='college_pics')
class Meta():
db_table='college'
def __str__(self):
return self.name
class Organizer(models.Model):
id=models.IntegerField(primary_key=True)
name=models.CharField(max_length=25)
phone=models.IntegerField()
def __str__(self):
return self.name
class Fest(models.Model):
FEST_CHOICES=[
('CUL','Cultural'),
('TEC','Technical'),
('COL','College'),
('SPO','Sports'),
]
id=models.IntegerField(primary_key=True)
name=models.CharField(max_length=50)
clg_id=models.ForeignKey(College,on_delete=models.CASCADE)
fest_type=models.CharField(choices=FEST_CHOICES,default='COL',max_length=10)
fest_desc=models.TextField(default='This is a fest')
#below two field are not showing up in admin page
start_date=models.DateField(auto_now_add=True)
end_date=models.DateField(auto_now_add=True)
event_nos=models.IntegerField()
org_id=models.ManyToManyField(Organizer)
image=models.ImageField(default='default.jpg',upload_to='fest_pics')
class Meta:
constraints = [
CheckConstraint(
check = Q(end_date__gte=F('start_date')),
name = 'check_start_date',
)
]
db_table='fest'
def __str__(self):
return self.name
The start_date and end_date attributes are the new ones added in this project. It was not there in the old one.
My admin.py file
from django.contrib import admin
from .models import College, Event, Fest, Organizer, Participated
admin.site.register(College)
admin.site.register(Organizer)
admin.site.register(Fest)
admin.site.register(Event)
admin.site.register(Participated)
But in my admin dashboard, while adding new fests I'm not getting the option to add start and end date.
I made migrations once again, fake migrated etc. What to do?
Is check constraint under model fest causing this problem?
They fields won't show up on Django Admin because they have auto_now_add=True so, the user shouldn't touch them.
You can make auto_now_add field display in admin by using readonly_fields in the admin class(this only show the data, you still can't edit it because it's auto_now_add)
#register with a class to use
class FestAdmin(admin.ModelAdmin):
readonly_fields = ('start_date', 'end_date')
admin.site.register(Fest, FestAdmin)

django-taggit on models with UUID as pk throwing out of range on save

I have models that uses UUID as its PK
class Foo(models.Model):
foo_id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
editable=False
)
tags = TaggableManager()
When I go and try to add a new tag
f = Foo.objects.latest('pk')
f.tags.add("testing")
I get DataError: integer out of range
When I import pdb on the cursor to view the SQL going in I see this.
(Pdb) params
(1, 287082253891563438098836942573405313042, 9)
(Pdb) sql
'INSERT INTO "taggit_taggeditem" ("tag_id", "object_id", "content_type_id") VALUES (%s, %s, %s) RETURNING "taggit_taggedit
m"."id"'
That long integer (287082253891563438098836942573405313042) trying to be insterted is obvsiouly the cause for the error. This number is the int of the UUID for foo_id
In [6]: foo.foo_id.int
Out[6]: 287082253891563438098836942573405313042
Is there something I can set to allow django-taggit to play nicely with contenttypes and UUID?
I'd like to extend #Pramod response, that was very helpful for me to find the right answer:
Taggit has another class that allows to change the behavior of the TaggedItem
Here is a snippet of how to implement this solution:
from django.db import models
from django.utils.translation import ugettext_lazy as _
from taggit.managers import TaggableManager
from taggit.models import GenericUUIDTaggedItemBase, TaggedItemBase
class UUIDTaggedItem(GenericUUIDTaggedItemBase, TaggedItemBase):
# If you only inherit GenericUUIDTaggedItemBase, you need to define
# a tag field. e.g.
# tag = models.ForeignKey(Tag, related_name="uuid_tagged_items", on_delete=models.CASCADE)
class Meta:
verbose_name = _("Tag")
verbose_name_plural = _("Tags")
class Food(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
# ... fields here
tags = TaggableManager(through=UUIDTaggedItem)
source: http://django-taggit.readthedocs.io/en/latest/custom_tagging.html#genericuuidtaggeditembase
Here's an answer based on Austin's comment.
In django-taggit, references to tagged models are stored in a model named GenericTaggedItemBase under a field called object_id. The object_id field is hardcoded to models.IntegerField. So it's not possible to tag models having UUID primary keys. The code is located here.
If all your models that need to be tagged are of the same type (in this case, models.UUIDField), then you can set object_id's type to models.UUIDField.
Here are the changes that have to be made, assuming you're using virtualenvwrapper
Locate the taggit package in the site packages folder. ~/virtualenvs/<your_virtualenv>/lib/<python_version>/site-packages/taggit
Copy the taggit directory into your project.
Delete the taggit directory from site-packages
In the models.py file in taggit, replace
object_id = models.IntegerField(verbose_name=_('Object id'), db_index=True) with
object_id = models.UUIDField(verbose_name=_('Object id'), db_index=True)
Migrate taggit.
python manage.py makemigrations taggit
python manage.py migrate

Django table naming convention. Can I change its behavior?

When Django creates tables it give them names of form app_class. I'm in the position of retrofitting a different (but basically similar in content) database to a Django installation. My tables name are not prepended with app_.
I could recreate my database and its tables accordingly but I'd like to see if Django has the flexibility to modify how it handles table names.
That is, I have a table coi_fs - I'd like to see if I can change the Django installation such that it will refer not to app_coi_fs but simply coi_fs?
If you already have a database I would recommend using the database introspection option. This will create the models needed to use your current database as is.
$ django-admin.py inspectdb > models.py
To answer your original question though, from the docs (https://docs.djangoproject.com/en/dev/ref/models/options/#table-names), you can use the db_table meta property.
models.py
class DjangoSite(models.Model):
domain = models.CharField(max_length=100)
name = models.CharField(max_length=50)
class Meta:
db_table = u'site'
Of course you can do it in Django, just change Model's metaclass:
from django.db.models.base import ModelBase
class ModelMetaClass(ModelBase):
def __new__(cls, name, bases, attrs):
new_class = super().__new__(cls, name, bases, attrs)
new_class._meta.db_table = some_awesome_logic_implemented_by_you()
return new_class
and then in the Model:
class DjangoSite(models.Model, metaclass=ModelMetaClass):
# __metaclass__ = ModelMetaClass # Python 2.x
class Meta:
pass
and you are done!
If you want to universally rename all tables, edit django source. This seems dangerous but if you explicitly want to change the Django installation than have at it. https://github.com/django/django/blob/master/django/db/models/options.py
old options.py
self.db_table = "%s_%s" % (self.app_label, self.model_name)
new options.py
self.db_table = self.model_name

How to make the ContentType foreignkey i18n?

I am developing an multilingual application using Django.
One part is to select the type of something using the ContentType API.
As describe in the doc, the ContentType object name is extracted from the verbose_name.
In my case the verbose_name is translated using xgettext_lazy but as it is copyied in the database during the syncdb, there is no translation for ContentType, the verbose_name is not translated.
I would like to be able to change the way the foreign key is displayed in a form.
Do you have any idea of how I can do that ?
Cheers,
Natim
You need to use ugettext_lazy instead of ugettext, and it's not stored in the database, but it's on some .po files. For instance:
from django.utils.translation import ugettext_lazy as _
class Event(models.Model):
...
class Meta:
verbose_name = _(u'Event')
verbose_name_plural = _(u'Events')
For code blocks that are loaded on import time, you need to use ugettext_lazy, and for those that are loaded on execution time, you need ugettext. Once you have that, you just need to do a "python manage.py makemessages" and "python manage.py compilemessages"
Finally here is the solution I found :
def content_type_choices(**kwargs):
content_types = []
for content_type in ContentType.objects.filter(**kwargs):
content_types.append((content_type.pk, content_type.model_class()._meta.verbose_name))
return content_types
LIMIT_CHOICES_TO = {'model__startswith': 'pageapp_'}
class PageWAForm(forms.ModelForm):
app_page_type = forms.ModelChoiceField(queryset=ContentType.objects.filter(**LIMIT_CHOICES_TO),
empty_label=None)
def __init__(self, *args, **kwargs):
super(PageWAForm, self).__init__(*args, **kwargs)
self.fields['app_page_type'].choices = content_type_choices(**LIMIT_CHOICES_TO)