Mixin fields not existing on Django migrations - python

Let's say I have this mixing:
class Test(object):
...some logic...
class TestMixin(models.Model):
db_field = django database field
test = Test() # not a database field
class Meta:
abstract = True
class Foo(TestMixin, models.Model):
... more db fields ...
I am having a strange issue here. If I inspect Foo through django shell I can see both fields, db_field and test
But if I create this migration:
from __future__ import unicode_literals
from django.db import migrations
def custom_operation(apps, schema_editor):
Foo = apps.get_model('django_app', 'Foo')
stuffs = Foo.objects.all()
for stuff in stuffs:
print stuff.test # this doesnt exist
class Migration(migrations.Migration):
dependencies = [
...
]
operations = [
migrations.RunPython(custom_operation),
]
And if I add a breakpoint at Test() __init__ it gets called through the shell or Django but not when running the migration.
Which is the differente between with using the model through the migration?

https://docs.djangoproject.com/en/1.11/topics/migrations/#historical-models
Specifically this quote:
Because it’s impossible to serialize arbitrary Python code, these historical models will not have any custom methods that you have defined. They will, however, have the same fields, relationships, managers (limited to those with use_in_migrations = True) and Meta options (also versioned, so they may be different from your current ones).

Related

Use managers in Factory-Boy for models

Use Factory-boy for retrieve operation without use the DB for testing case.
I have this simple model:
class Student(models.Model):
name = models.CharField(max_length=20) `
To get all: Student.objects.all()
With Factory-boy:
class StudentFactory(factory.django.DjangoModelFactory):
class Meta:
model = Student
Is there a way to make StudentFactory.objects.all() ?
When I call the method all() in my factory, I would like to return a list of QuerySet created by me. Example: [QuerySet_1, QuerySet_2] # Not Database.
With that, I can change my data from DB to memory in my test.
You may be looking for the methods create_batch and build_batch, depending on whether you want to save the newly generated instances in the test database or not.
Here's an example which I copy-pasted and adapted from factory-boy documentation:
# --- models.py
class StudentFactory(factory.django.DjangoModelFactory):
class Meta:
model = Student
# --- test_student_factory.py
from . import factories
def make_objects():
factories.StudentFactory.create_batch(size=50)

How can I enforce inheritance in my Django models?

In my Django app, I have an abstract model called MyModel that has e.g. created_at and updated_at fields. I want all the models in my project to subclass MyModel rather than using django.db.models.Model directly.
We have several developers on our app, so I want to use some sort of linter or CI check to enforce that this happens. How can I do this?
As mentioned by Willem Van Onsem in their comment you can write your own checks using the System check framework.
Assuming we have the following models in an app named "checktest" with Parent being the model that all models should inherit from:
from django.db import models
class Parent(models.Model):
class Meta:
abstract = True
# This should raise an error
class Foo(models.Model):
pass
# This should be fine
class Bar(Parent):
pass
We'll write a check as follows in a file checktest/custom_checks.py, note that the list APPS_TO_TEST contains the names of the apps whose models should inherit from the parent class:
from django.apps import apps
from django.core.checks import Error, register, Tags
from .models import Parent
# List of apps that should inherit from Parent
APPS_TO_TEST = ["checktest"]
#register(Tags.models)
def model_must_inherit(app_configs, **kwargs):
errors = []
for app in APPS_TO_TEST:
models = apps.get_app_config(app).get_models()
for model in models:
if not issubclass(model, Parent):
errors.append(Error(
f"Model {model.__name__} does not inherit from Parent",
hint="Models must inherit from the Parent class",
obj=model,
id="checktest.E001"
))
return errors
In the app configs ready method we'll import the above file so that the check will get run:
from django.apps import AppConfig
class ChecktestConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'checktest'
def ready(self) -> None:
from . import custom_checks
Now whenever we run commands like runserver or migrate the checks will get implicitly run. In a CI environment you can explicitly run the checks using the check command.

Does using a foreign key as a column in the django admin interface create a new database query for each row?

I have a model relationship that looks like the following:
class Author(Model):
first_name = CharField()
class Book(Model):
title = CharField()
author = ForeignKey(Author)
I want an admin interface like this:
class BookAdmin(ModelAdmin):
list_display = ('title', 'author_name')
def author_name(self, obj):
return obj.author.name
Does this create a new query for each row, or is django smart about using a join here? What if the relation is 2 or more levels deep instead of just 1?
Using python 3.8, django 3.1, and Postgres 12.
Just tested this, and yes it seems to create a new query for each row. To confirm, you can use DEBUG = True in your settings and do something like this:
class BookAdmin(ModelAdmin):
list_display = ('title', 'author_name')
def author_name(self, obj):
name = obj.author.name
from django.db import connection
print(len(connection.queries))
return name
This will print the number of queries that are run, at the time when a row is rendered. You can observe that it increases as your rows are rendered.
Fortunately you can override get_queryset to add select_related, to improve the queries, so:
class BookAdmin(ModelAdmin):
list_display = ('title', 'author_name')
def get_queryset(self, request):
queryset = super().get_queryset(request)
return queryset.select_related('author')
def author_name(self, obj):
return obj.author.name
This gave me a constant number of queries which is ideal.
See the docs FAQ: "How can I see the raw SQL queries Django is running?"
Make sure your Django DEBUG setting is set to True. Then do this:
from django.db import connection
print(connection.queries)
This gives you all the SQL queries Django executed. From there you can inspect whether it uses joins or uses separate query for ForeignKey

Django error while trying to create custom model class

I started organizing my models in a package as specified here : https://docs.djangoproject.com/en/1.11/topics/db/models/#organizing-models-in-a-package
I'm using a legacy Oracle database
I also created a module containing some extensions/inheritances of the Model class, to facilitate creating multiple classes that contain repeated fields
This is my structure :
models/
__init__.py
geo_classes.py
tables.py
The error is the following :
django.db.utils.DatabaseError: ORA-00904:
"TABLE_NAME"."TABLECLASS_PTR_ID": invalid identifier
I couldn't find anything online about this PTR_ID it is trying to catch, maybe I missed something about extending base Models?
Files (only the important parts) :
geo_classes.py :
from django.db import models
class EsriTable(models.Model):
objectid = models.BigIntegerField(unique=True, editable=False, verbose_name='OBJECTID')
class TableClass(EsriTable):
cod = models.BigIntegerField(primary_key=True)
def __str__(self):
return str(self.cod)
tables.py :
from .geo_classes import TableClass
from django.db import models
class MyClass(TableClass):
#Fields
name = models.CharField(max_length=50)
#Keys
#Relations
class Meta:
managed = False
db_table = 'TABLE_NAME'
You are using multi table inheritance. Each of your models is a separate table, including the base class. Django sets a pointer id to point to the parent table.
However, that's clearly not what you want. Neither of your base classes are actually tables on their own. So you need to use abstract inheritance: give both of those models their own inner Meta class, and set abstract = True.

Can you give a Django app a verbose name for use throughout the admin?

In the same way that you can give fields and models verbose names that appear in the Django admin, can you give an app a custom name?
Django 1.8+
Per the 1.8 docs (and current docs),
New applications should avoid default_app_config. Instead they should require the dotted path to the appropriate AppConfig subclass to be configured explicitly in INSTALLED_APPS.
Example:
INSTALLED_APPS = [
# ...snip...
'yourapp.apps.YourAppConfig',
]
Then alter your AppConfig as listed below.
Django 1.7
As stated by rhunwicks' comment to OP, this is now possible out of the box since Django 1.7
Taken from the docs:
# in yourapp/apps.py
from django.apps import AppConfig
class YourAppConfig(AppConfig):
name = 'yourapp'
verbose_name = 'Fancy Title'
then set the default_app_config variable to YourAppConfig
# in yourapp/__init__.py
default_app_config = 'yourapp.apps.YourAppConfig'
Prior to Django 1.7
You can give your application a custom name by defining app_label in your model definition. But as django builds the admin page it will hash models by their app_label, so if you want them to appear in one application, you have to define this name in all models of your application.
class MyModel(models.Model):
pass
class Meta:
app_label = 'My APP name'
As stated by rhunwicks' comment to OP, this is now possible out of the box since Django 1.7
Taken from the docs:
# in yourapp/apps.py
from django.apps import AppConfig
class YourAppConfig(AppConfig):
name = 'yourapp'
verbose_name = 'Fancy Title'
then set the default_app_config variable to YourAppConfig
# in yourapp/__init__.py
default_app_config = 'yourapp.apps.YourAppConfig'
If you have more than one model in the app just create a model with the Meta information and create subclasses of that class for all your models.
class MyAppModel(models.Model):
class Meta:
app_label = 'My App Label'
abstract = True
class Category(MyAppModel):
name = models.CharField(max_length=50)
Well I started an app called todo and have now decided I want it to be named Tasks. The problem is that I already have data within my table so my work around was as follows. Placed into the models.py:
class Meta:
app_label = 'Tasks'
db_table = 'mytodo_todo'
Hope it helps.
Give them a verbose_name property.
Don't get your hopes up. You will also need to copy the index view from django.contrib.admin.sites into your own ProjectAdminSite view and include it in your own custom admin instance:
class ProjectAdminSite(AdminSite):
def index(self, request, extra_context=None):
copied stuff here...
admin.site = ProjectAdminSite()
then tweak the copied view so that it uses your verbose_name property as the label for the app.
I did it by adding something a bit like this to the copied view:
try:
app_name = model_admin.verbose_name
except AttributeError:
app_name = app_label
While you are tweaking the index view why not add an 'order' property too.
First you need to create a apps.py file like this on your appfolder:
# appName/apps.py
# -*- coding: utf-8 -*-
from django.apps import AppConfig
class AppNameConfig(AppConfig):
name = 'appName'
verbose_name = "app Custom Name"
To load this AppConfig subclass by default:
# appName/__init__.py
default_app_config = 'appName.apps.AppNameConfig'
Is the best way to do. tested on Django 1.7
For the person who had problems with the Spanish
This code enable the utf-8 compatibility on python2 scripts
# -*- coding: utf-8 -*-
For Django 1.4 (not yet released, but trunk is pretty stable), you can use the following method. It relies on the fact that AdminSite now returns a TemplateResponse, which you can alter before it is rendered.
Here, we do a small bit of monkey patching to insert our behaviour, which can be avoided if you use a custom AdminSite subclass.
from functools import wraps
def rename_app_list(func):
m = {'Sites': 'Web sites',
'Your_app_label': 'Nicer app label',
}
#wraps(func)
def _wrapper(*args, **kwargs):
response = func(*args, **kwargs)
app_list = response.context_data.get('app_list')
if app_list is not None:
for a in app_list:
name = a['name']
a['name'] = m.get(name, name)
title = response.context_data.get('title')
if title is not None:
app_label = title.split(' ')[0]
if app_label in m:
response.context_data['title'] = "%s administration" % m[app_label]
return response
return _wrapper
admin.site.__class__.index = rename_app_list(admin.site.__class__.index)
admin.site.__class__.app_index = rename_app_list(admin.site.__class__.app_index)
This fixes the index and the app_index views. It doesn't fix the bread crumbs in all other admin views.
No, but you can copy admin template and define app name there.
There is a hack that can be done that does not require any migrations.
Taken from Ionel's blog and credit goes to him: http://blog.ionelmc.ro/2011/06/24/custom-app-names-in-the-django-admin/
There is also a ticket for this that should be fixed in Django 1.7 https://code.djangoproject.com/ticket/3591
"""
Suppose you have a model like this:
class Stuff(models.Model):
class Meta:
verbose_name = u'The stuff'
verbose_name_plural = u'The bunch of stuff'
You have verbose_name, however you want to customise app_label too for different display in admin. Unfortunatelly having some arbitrary string (with spaces) doesn't work and it's not for display anyway.
Turns out that the admin uses app_label. title () for display so we can make a little hack: str subclass with overriden title method:
class string_with_title(str):
def __new__(cls, value, title):
instance = str.__new__(cls, value)
instance._title = title
return instance
def title(self):
return self._title
__copy__ = lambda self: self
__deepcopy__ = lambda self, memodict: self
Now we can have the model like this:
class Stuff(models.Model):
class Meta:
app_label = string_with_title("stuffapp", "The stuff box")
# 'stuffapp' is the name of the django app
verbose_name = 'The stuff'
verbose_name_plural = 'The bunch of stuff'
and the admin will show "The stuff box" as the app name.
"""
If you already have existing tables using the old app name, and you don't want to migrate them, then just set the app_label on a proxy of the original model.
class MyOldModel(models.Model):
pass
class MyNewModel(MyOldModel):
class Meta:
proxy = True
app_label = 'New APP name'
verbose_name = MyOldModel._meta.verbose_name
Then you just have to change this in your admin.py:
#admin.site.register(MyOldModel, MyOldModelAdmin)
admin.site.register(MyNewModel, MyOldModelAdmin)
Be aware that the url will be /admin/NewAPPname/mynewmodel/ so you might just want to make sure that the class name for the new model looks as close to the old model as possible.
Well, this works for me. In the app.py use this:
class MainConfig(AppConfig):
name = 'main'
verbose_name="Fancy Title"
In setting.py add the name of App and the class name present in app.py file in App folder
INSTALLED_APPS = [
'main.apps.MainConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
That's simple just add as follow on your appName/apps
class AppNameConfig(AppConfig):
default_auto_field = 'default Django'
name = 'AppName'
verbose_name = 'Name you Want'
The following plug-and-play piece of code works perfectly since Django 1.7. All you have to do is copy the below code in the __init__.py file of the specific app and change the VERBOSE_APP_NAME parameter.
from os import path
from django.apps import AppConfig
VERBOSE_APP_NAME = "YOUR VERBOSE APP NAME HERE"
def get_current_app_name(file):
return path.dirname(file).replace('\\', '/').split('/')[-1]
class AppVerboseNameConfig(AppConfig):
name = get_current_app_name(__file__)
verbose_name = VERBOSE_APP_NAME
default_app_config = get_current_app_name(__file__) + '.__init__.AppVerboseNameConfig'
If you use this for multiple apps, you should factor out the get_current_app_name function to a helper file.
Update for 2021 (Django 3.2):
Inside the app.py file of your App you find the AppClientsConfig class. Inside you find the name attribute. Don't change that unless you really have to (and then you will need to also change the app registry inside the settings.py file...etc..).
Instead add an attribute to the class like so to it:
verbose_name = "The Name You Like"
That should do the trick. Feel free to read up more about it on the official Django documentation page.

Categories

Resources