How to override a package method in Django? - python

I'm trying to override a parent class method that I've installed via pip. But, the problem is that I'm not sure how my overridden class will be called so that it will use my method instead of parent class method. This is the method I'm trying to override:
class OIDCAuthenticationBackend(ModelBackend):
def verify_claims(self, claims):
"""Verify the provided claims to decide if authentication should be allowed."""
# Verify claims required by default configuration
scopes = self.get_settings('OIDC_RP_SCOPES', 'openid email')
if 'email' in scopes.split():
return 'email' in claims
LOGGER.warning('Custom OIDC_RP_SCOPES defined. '
'You need to override `verify_claims` for custom claims verification.')
return True
And my overridden method is :
models.py
from mozilla_django_oidc.auth import OIDCAuthenticationBackend
class MyOIDCAB(OIDCAuthenticationBackend):
def verify_claims(self, claims):
return True
I've written this on models.py according to some docs I read for overriding but I'm not sure where should I actually write this.
then trying to call it from my view functions like this:
views.py
from myapp import MyOIDCAB
but then I got this error:
from myapp import MyOIDCAB
ImportError: cannot import name 'MyOIDCAB'
I'm not sure I'm calling it in the right way or wrong?
My project structure is:
myproject
myapp
templates
manage.py
From the package site, they call it in the template like this:
Login

The class is in models.py file so here's how you should import that :
from myapp.models import MyOIDCAB

Related

Django overriding default managers of contrib models

I have a Django app named site_settings in which SiteSettings model is defined. This model contains a foreign key to django.contrib.sites.models.Site model. Now I want to override default objects manager of Site model with new one I defined:
from django.contrib.sites.models import SiteManager as _OrigSiteManager
class SiteManager(_OrigSiteManager):
...
I tried:
Site.add_to_class("objects", SiteManager())
But it didn't work. The problem is I am adding to class with already existing objects name. When adding with another name, it works as expected:
Site.add_to_class("my_objects", SiteManager()) # now Site.my_objects points to my custom manager
But I want to override existing objects manager with my custom manager. How can I do that?
You can redefine and use your own Site class everywhere:
from django.contrib.sites.models import Site
class Site(Site):
...
objects = SiteManager()
or you can use contribute_to_class:
from django.contrib.sites.models import Site
SiteManager().contribute_to_class(Site, 'objects')
or you can made monkey patch:
from django.contrib.sites.models import Site
Site.objects = SiteManager(model=Site)
If you need it only to change queryset in foreignkey to Site, you can use limit_choices_to:
foreignkey(Site, limit_choices_to=Q(your query to limit sites queryset))

Cannot import models from another app in Django

so I have 2 apps running in the same project.
My files are structured as follows:
/project_codebase
/project
__init.py
settings.py
urls.py
wsgi.py
...
/app1
...
/app2
...
manage.py
So, I for some weird reason have a different name for my base directory (that is, it ends with codebase). Hopefully, that is not an issue.
In my settings.py, I have this:
INSTALLED_APPS = [
...
'app1',
'app2',
]
Ok, so in my models.py (from app2), I can easily import models from app1 with from app1.models import *, however, when I use from app2.models import * in my models.py (from app1), I get an ImportError.
Any solutions to this?
This might be due to circular import issues. To avoid this you should load the model dynamically:
For recent versions of django (1.7+) use the application registry:
from django.apps import apps
MyModel1 = apps.get_model('app1', 'MyModel1')
For earlier django versions (<1.7):
from django.db.models.loading import get_model
MyModel1 = get_model('app1', 'MyModel1')
Note 1: If you want to define a ForeignKey relationship, there is no need for a separate import statement. Django has you covered on this:
If app1 is an installed app, you should define the ForeignKey relationship as follows:
# in app2.py
class MyModel2(models.Model):
mymodel1 = models.ForeignKey('app1.MyModel1')
Note 2: The get_model only works if app1 is an installed app and MyModel1 is the model you want to import from app1.
Note 3: Try to avoid wildcard import (from ... import *), as this is bad practice.
It's definitely a circular import.
But i think is what you need is to use models as some sort of RetationFields(ForeignKey, ManyToManyField or OneToOneField) arguments. So you need to skip import and use as so:
# app1/models.py
class Model1(models.Model):
relation_field = models.ForeignKey('app2.Model2')
From docs:
If you need to create a relationship on a model that has not yet been defined, you can use the name of the model, rather than the model object itself
To refer to models defined in another application, you can explicitly specify a model with the full application label
Just put str object as first argument to relation fields that leeds to <app_name>.<Model_name>.
Note: it's better to avoid importing everything from module(from <module_name> import *)
If you want to import only some specific module then do not use import *.
It will take more time load your all library and so can affect the speed of your app also.
If you want to use few modules from your second app then just add module name instead of whole libraries something like this:
from app2.models import Module1, Module2
or it may be circular import issue as other clarify.
Thanks.
i use this code always and it's work :)
from position_app.models import Member
You need to specify the model names you want to import, for ex from app1.models import ModelName1, ModelName2.
Make sure there is no name clash between one of your apps and one of the modules installed in your Python environment. If you use pip, you can run pip freezeto see a list of installed modules.
I had the same error when one of my apps was named 'packaging', and the packaging python module was installed.
I also face this problem when I try to import my model from another app in (django2.2)
But at last I Imported It and Its successfully working.
here is my two app:
INSTALLED_APPS = [
...
'categories',
'videos',
]
and this is the code for how I Imported it into videos/models.py file as a ForeignKey Connectivity
from django.db import models
class Videos(models.Model):
categories = models.ForeignKey('categories.Categories', related_name='categories', on_delete=models.CASCADE)
If want to see my Categories Model from categories/models.py file, you can check this code otherwise neglect it
from django.db import models
class Categories(models.Model):
category_name = models.CharField(max_length=50)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
It is a circular import
In my case I needed the imported class not for a relation field, but for use it inside a method instead.
If that's the case, I suggest to import inside the method, otherwise an AppRegistryNotReady("Models aren't loaded yet.") is raised.
class Student(CustomUser):
""" Usuario alumno """
class Meta:
verbose_name = "Alumno"
verbose_name_plural = "Alumnos"
def get_current_school_class(self):
""" Obtiene el curso actual de un alumno """
from school.models import SchoolClass, StudentClass
# proceed with the method...
it's not necessary to import models from others apps
just put the app.models in the foreignkey field and that's work ;)
app 1:
class model1(models.Model):
field=models.field type ...
app 2:
class model2(models.Model):
field=models.ForeignKey('app1.model1', on_delete. ...)
to avoid code correction :D

'MyAppConfig' must supply a name attribute

i am try to run some piece of code when starting an apache server or django development server. I am using the AppConfig class to do this..(following the docs).
But while starting the server i am get the following error.
django.core.exceptions.ImproperlyConfigured: '{app_name}.apps.MyAppConfig' must supply a name attribute.
following is the sample code:
from django.apps import AppConfig
class MyAppConfig(AppConfig):
def ready():
#my stuff
I don't have any idea about the error.
The docs you linked to tell you what name should be:
AppConfig.name
Full Python path to the application, e.g. 'django.contrib.admin'.
This attribute defines which application the configuration applies to. It must be set in all AppConfig subclasses.
It must be unique across a Django project.
So if your app is call app_name, then you need to set name = 'app_name'.
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = 'app_name'
def ready(self):
# my stuff

Help with Admin forms validation error

I am quite new to Django, I'm having few problems with validation
forms in Admin module, more specifically with raising exceptions in the
ModelForm. I can validate and manipulate data in clean methods but
cannot seem to raise any errors. Whenever I include any raise
statement I get this error "'NoneType' object has no attribute
'ValidationError'". When I remove the raise part everything works
fine.
Then if I reimport django.forms (inside clean method) with a different alias (e.g. from django import forms as blahbalh) then I'm able to raise messages using blahblah.ValidateException.
Any tips or suggestions on doing such a thing properly ?
Here's an example of what I'm doing in Admin.py:
admin.py
from django import forms
from proj.models import *
from django.contrib import admin
class FontAdminForm(forms.ModelForm):
class Meta:
model = Font
def clean_name(self):
return self.cleaned_data["name"].upper()
def clean_description(self):
desc = self.cleaned_data['description']
if desc and if len(desc) < 10:
raise forms.ValidationError('Description is too short.')
return desc
class FontAdmin(admin.ModelAdmin):
form = FontAdminForm
list_display = ['name', 'description']
admin.site.register(Font, FontAdmin)
--
Thanks,
A
You problem might be in the * import.
from proj.models import *
if proj.models contains any variable named forms (including some module import like "from django import forms), it could trounce your initial import of:
from django import forms
I would explicitly import from proj.models, e.g.
from proj.models import Font
If that doesn't work, see if there are any other variables name "forms" that could be messing with your scope.
You can use introspection to see what "forms" is. Inside your clean_description method:
print forms.__package__
My guess is it is not going to be "django" (or will return an error, indicating that it is definitely not django.forms).

Django: How to create a model dynamically just for testing

I have a Django app that requires a settings attribute in the form of:
RELATED_MODELS = ('appname1.modelname1.attribute1',
'appname1.modelname2.attribute2',
'appname2.modelname3.attribute3', ...)
Then hooks their post_save signal to update some other fixed model depending on the attributeN defined.
I would like to test this behaviour and tests should work even if this app is the only one in the project (except for its own dependencies, no other wrapper app need to be installed). How can I create and attach/register/activate mock models just for the test database? (or is it possible at all?)
Solutions that allow me to use test fixtures would be great.
You can put your tests in a tests/ subdirectory of the app (rather than a tests.py file), and include a tests/models.py with the test-only models.
Then provide a test-running script (example) that includes your tests/ "app" in INSTALLED_APPS. (This doesn't work when running app tests from a real project, which won't have the tests app in INSTALLED_APPS, but I rarely find it useful to run reusable app tests from a project, and Django 1.6+ doesn't by default.)
(NOTE: The alternative dynamic method described below only works in Django 1.1+ if your test case subclasses TransactionTestCase - which slows down your tests significantly - and no longer works at all in Django 1.7+. It's left here only for historical interest; don't use it.)
At the beginning of your tests (i.e. in a setUp method, or at the beginning of a set of doctests), you can dynamically add "myapp.tests" to the INSTALLED_APPS setting, and then do this:
from django.core.management import call_command
from django.db.models import loading
loading.cache.loaded = False
call_command('syncdb', verbosity=0)
Then at the end of your tests, you should clean up by restoring the old version of INSTALLED_APPS and clearing the app cache again.
This class encapsulates the pattern so it doesn't clutter up your test code quite as much.
#paluh's answer requires adding unwanted code to a non-test file and in my experience, #carl's solution does not work with django.test.TestCase which is needed to use fixtures. If you want to use django.test.TestCase, you need to make sure you call syncdb before the fixtures get loaded. This requires overriding the _pre_setup method (putting the code in the setUp method is not sufficient). I use my own version of TestCase that lets me add apps with test models. It is defined as follows:
from django.conf import settings
from django.core.management import call_command
from django.db.models import loading
from django import test
class TestCase(test.TestCase):
apps = ()
def _pre_setup(self):
# Add the models to the db.
self._original_installed_apps = list(settings.INSTALLED_APPS)
for app in self.apps:
settings.INSTALLED_APPS.append(app)
loading.cache.loaded = False
call_command('syncdb', interactive=False, verbosity=0)
# Call the original method that does the fixtures etc.
super(TestCase, self)._pre_setup()
def _post_teardown(self):
# Call the original method.
super(TestCase, self)._post_teardown()
# Restore the settings.
settings.INSTALLED_APPS = self._original_installed_apps
loading.cache.loaded = False
I shared my solution that I use in my projects. Maybe it helps someone.
pip install django-fake-model
Two simple steps to create fake model:
1) Define model in any file (I usualy define model in test file near a test case)
from django_fake_model import models as f
class MyFakeModel(f.FakeModel):
name = models.CharField(max_length=100)
2) Add decorator #MyFakeModel.fake_me to your TestCase or to test function.
class MyTest(TestCase):
#MyFakeModel.fake_me
def test_create_model(self):
MyFakeModel.objects.create(name='123')
model = MyFakeModel.objects.get(name='123')
self.assertEqual(model.name, '123')
This decorator creates table in your database before each test and remove the table after test.
Also you may create/delete table manually: MyFakeModel.create_table() / MyFakeModel.delete_table()
I've figured out a way for test-only models for django 1.7+.
The basic idea is, make your tests an app, and add your tests to INSTALLED_APPS.
Here's an example:
$ ls common
__init__.py admin.py apps.py fixtures models.py pagination.py tests validators.py views.py
$ ls common/tests
__init__.py apps.py models.py serializers.py test_filter.py test_pagination.py test_validators.py views.py
And I have different settings for different purposes(ref: splitting up the settings file), namely:
settings/default.py: base settings file
settings/production.py: for production
settings/development.py: for development
settings/testing.py: for testing.
And in settings/testing.py, you can modify INSTALLED_APPS:
settings/testing.py:
from default import *
DEBUG = True
INSTALLED_APPS += ['common', 'common.tests']
And make sure that you have set a proper label for your tests app, namely,
common/tests/apps.py
from django.apps import AppConfig
class CommonTestsConfig(AppConfig):
name = 'common.tests'
label = 'common_tests'
common/tests/__init__.py, set up proper AppConfig(ref: Django Applications).
default_app_config = 'common.tests.apps.CommonTestsConfig'
Then, generate db migration by
python manage.py makemigrations --settings=<your_project_name>.settings.testing tests
Finally, you can run your test with param --settings=<your_project_name>.settings.testing.
If you use py.test, you can even drop a pytest.ini file along with django's manage.py.
py.test
[pytest]
DJANGO_SETTINGS_MODULE=kungfu.settings.testing
Quoting from a related answer:
If you want models defined for testing only then you should check out
Django ticket #7835 in particular comment #24 part of which
is given below:
Apparently you can simply define models directly in your tests.py.
Syncdb never imports tests.py, so those models won't get synced to the
normal db, but they will get synced to the test database, and can be
used in tests.
This solution works only for earlier versions of django (before 1.7). You can check your version easily:
import django
django.VERSION < (1, 7)
Original response:
It's quite strange but form me works very simple pattern:
add tests.py to app which you are going to test,
in this file just define testing models,
below put your testing code (doctest or TestCase definition),
Below I've put some code which defines Article model which is needed only for tests (it exists in someapp/tests.py and I can test it just with: ./manage.py test someapp ):
class Article(models.Model):
title = models.CharField(max_length=128)
description = models.TextField()
document = DocumentTextField(template=lambda i: i.description)
def __unicode__(self):
return self.title
__test__ = {"doctest": """
#smuggling model for tests
>>> from .tests import Article
#testing data
>>> by_two = Article.objects.create(title="divisible by two", description="two four six eight")
>>> by_three = Article.objects.create(title="divisible by three", description="three six nine")
>>> by_four = Article.objects.create(title="divisible by four", description="four four eight")
>>> Article.objects.all().search(document='four')
[<Article: divisible by two>, <Article: divisible by four>]
>>> Article.objects.all().search(document='three')
[<Article: divisible by three>]
"""}
Unit tests also working with such model definition.
I chose a slightly different, albeit more coupled, approach to dynamically creating models just for testing.
I keep all my tests in a tests subdirectory that lives in my files app. The models.py file in the tests subdirectory contains my test-only models. The coupled part comes in here, where I need to add the following to my settings.py file:
# check if we are testing right now
TESTING = 'test' in sys.argv
if TESTING:
# add test packages that have models
INSTALLED_APPS += ['files.tests',]
I also set db_table in my test model, because otherwise Django would have created the table with the name tests_<model_name>, which may have caused a conflict with other test models in another app. Here's my my test model:
class Recipe(models.Model):
'''Test-only model to test out thumbnail registration.'''
dish_image = models.ImageField(upload_to='recipes/')
class Meta:
db_table = 'files_tests_recipe'
Here's the pattern that I'm using to do this.
I've written this method that I use on a subclassed version of TestCase. It goes as follows:
#classmethod
def create_models_from_app(cls, app_name):
"""
Manually create Models (used only for testing) from the specified string app name.
Models are loaded from the module "<app_name>.models"
"""
from django.db import connection, DatabaseError
from django.db.models.loading import load_app
app = load_app(app_name)
from django.core.management import sql
from django.core.management.color import no_style
sql = sql.sql_create(app, no_style(), connection)
cursor = connection.cursor()
for statement in sql:
try:
cursor.execute(statement)
except DatabaseError, excn:
logger.debug(excn.message)
pass
Then, I create a special test-specific models.py file in something like myapp/tests/models.py that's not included in INSTALLED_APPS.
In my setUp method, I call create_models_from_app('myapp.tests') and it creates the proper tables.
The only "gotcha" with this approach is that you don't really want to create the models ever time setUp runs, which is why I catch DatabaseError. I guess the call to this method could go at the top of the test file and that would work a little better.
Combining your answers, specially #slacy's, I did this:
class TestCase(test.TestCase):
initiated = False
#classmethod
def setUpClass(cls, *args, **kwargs):
if not TestCase.initiated:
TestCase.create_models_from_app('myapp.tests')
TestCase.initiated = True
super(TestCase, cls).setUpClass(*args, **kwargs)
#classmethod
def create_models_from_app(cls, app_name):
"""
Manually create Models (used only for testing) from the specified string app name.
Models are loaded from the module "<app_name>.models"
"""
from django.db import connection, DatabaseError
from django.db.models.loading import load_app
app = load_app(app_name)
from django.core.management import sql
from django.core.management.color import no_style
sql = sql.sql_create(app, no_style(), connection)
cursor = connection.cursor()
for statement in sql:
try:
cursor.execute(statement)
except DatabaseError, excn:
logger.debug(excn.message)
With this, you don't try to create db tables more than once, and you don't need to change your INSTALLED_APPS.
If you are writing a reusable django-app, create a minimal test-dedicated app for it!
$ django-admin.py startproject test_myapp_project
$ django-admin.py startapp test_myapp
add both myapp and test_myapp to the INSTALLED_APPS, create your models there and it's good to go!
I have gone through all these answers as well as django ticket 7835, and I finally went for a totally different approach.
I wanted my app (somehow extending queryset.values() ) to be able to be tested in isolation; also, my package does include some models and I wanted a clean distinction between test models and package ones.
That's when I realized it was easier to add a very small django project in the package!
This also allows a much cleaner separation of code IMHO:
In there you can cleanly and without any hack define your models, and you know they will be created when you run your tests from in there!
If you are not writing an independent, reusable app you can still go this way: create a test_myapp app, and add it to your INSTALLED_APPS only in a separate settings_test_myapp.py!
Someone already mentioned Django ticket #7835, but there appears to be a more recent reply that looks much more promising for more recent versions of Django. Specifically #42, which proposes a different TestRunner:
from importlib.util import find_spec
import unittest
from django.apps import apps
from django.conf import settings
from django.test.runner import DiscoverRunner
class TestLoader(unittest.TestLoader):
""" Loader that reports all successful loads to a runner """
def __init__(self, *args, runner, **kwargs):
self.runner = runner
super().__init__(*args, **kwargs)
def loadTestsFromModule(self, module, pattern=None):
suite = super().loadTestsFromModule(module, pattern)
if suite.countTestCases():
self.runner.register_test_module(module)
return suite
class RunnerWithTestModels(DiscoverRunner):
""" Test Runner that will add any test packages with a 'models' module to INSTALLED_APPS.
Allows test only models to be defined within any package that contains tests.
All test models should be set with app_label = 'tests'
"""
def __init__(self, *args, **kwargs):
self.test_packages = set()
self.test_loader = TestLoader(runner=self)
super().__init__(*args, **kwargs)
def register_test_module(self, module):
self.test_packages.add(module.__package__)
def setup_databases(self, **kwargs):
# Look for test models
test_apps = set()
for package in self.test_packages:
if find_spec('.models', package):
test_apps.add(package)
# Add test apps with models to INSTALLED_APPS that aren't already there
new_installed = settings.INSTALLED_APPS + tuple(ta for ta in test_apps if ta not in settings.INSTALLED_APPS)
apps.set_installed_apps(new_installed)
return super().setup_databases(**kwargs)

Categories

Resources