How to iterate through classes of file in python? - python

I have a file called models with few classes and I need to iterate throw them in order to get each class to preform the same task on it.
my problem:
from django.contrib import admin
from . import models
# this is very repetetive
admin.site.register(models.Myclass)
admin.site.register(models.MySecondclass)
admin.site.register(models.MyThirdclass)
#.
#.
#...
my goal:
from django.contrib import admin
from . import models
for class in models:
admin.site.register(class)
If you can't understand django
# models.py
import example
class Component():
return example.Component
class Post():
return "Post"
class Comment():
return "Comment"
# the goal file
from . import models
# i need to get my models as list [Component,Post,Comment]

Using inspect.getmembers with passing inspect.isclass as its second argument for predicate will give classes. To further filter to prevent other classes imported from somewhere else, ensure __module__ is "models":
import inspect
classes = [obj
for _, obj in inspect.getmembers(models, predicate=inspect.isclass)
if obj.__module__ == "models"]
gives classes as
[models.Component, models.Post, models.Comment]
after which you can do your for:
for class_ in classes:
admin.site.register(class_)
(using class_ to not override class of Python).

Related

Why using validators=[URLValidator] when defining a model CharField doesn't make it check for URL-validity upon saving the corresponding ModelForm?

app.models.py:
from django.core.validators import URLValidator
from django.db import models
class Snapshot(models.Model):
url = models.CharField(max_length=1999, validators=[URLValidator])
app.forms.py:
from django import forms
from .models import Snapshot
class SnapshotForm(forms.ModelForm):
class Meta:
model = Snapshot
fields = ('url',)
app.views.py:
from django.http import HttpResponse
from .forms import SnapshotForm
def index(request):
snapshot_form = SnapshotForm(data={'url': 'not an URL!'})
if snapshot_form.is_valid():
snapshot_form.save()
return HttpResponse("")
Why does it save 'not an URL!' into DB, despite the fact that it ain't a valid URL?!
What is the right way to incorporate URLValidator?
You've specified the validators for your field like so: validators=[URLValidator]. This is incorrect because the validators argument takes in a list of callables that perform the validation. In this case even though URLValidator is callable but that's actually the __init__ method of the class being called. You need to pass an instance of the class for it to work properly:
# Note the brackets after URLValidator to instantiate the class
url = models.CharField(max_length=1999, validators=[URLValidator()])
Better yet since you want to take input a URL you should simply use the URLField class for your field:
url = models.URLField(max_length=1999)

How to get all models of a class in all apps of a Django project

I have a model class AppModelActions that inherits from CustomModel that inherits from django.db.models, and I use it in all apps of the project.
I want to get all instances of AppModelActions in all apps, from a command file in one of the apps. I can't import each one explicitly because I want this command to work dynamically. I tried to import the instances programmatically, but it didn't work (I get this error: `KeyError: 'AppModelActions').
This is my code:
from django.core.management.base import BaseCommand
from django.db import IntegrityError
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from GeneralApp import models
class Command(BaseCommand):
help = _("""Run this commando to populate an empty database with initial data required for Attractora to work.
For the momment, this models are considered on 'populate_db' command:
- InventoriManagerApp.InventoryManagerAppActions """)
def populate_db(self):
applications = settings.INSTALLED_APPS
for application in applications:
try:
import applications.models
except:
pass
#from InventoryManagerApp.models import InventoryManagerAppActions
models_to_populate = (vars()['AppModelActions'])
#models_to_populate = (InventoryManagerAppActions,)
for model_to_populate in models_to_populate:
self.populate_model('InventoryManagerApp', model_to_populate)
def populate_model(self, app_label, model_to_populate):
model_to_populate_instance = model_to_populate()
content_type = ContentType.objects.get(app_label=app_label, model=model_to_populate_instance.name)
if hasattr(model_to_populate_instance, 'populate_data'):
populate_data = model_to_populate.populate_data
for record in populate_data:
for key, value in record.items():
setattr(model_to_populate_instance, key, value)
try:
model_to_populate_instance.save()
except IntegrityError:
print("{} already exists.".format(model_to_populate_instance))
# else:
# new_permission = Permission.objects.create(code_name=)
def handle(self, *args, **kwargs):
self.populate_db()
You can obtain all subclasses by iteratively updating a set of children:
from django.core.management.base import BaseCommand
class Command(BaseCommand):
# ...
def populate_db(self):
from some_app.models import AppModelActions
children = set()
gen = [AppModelActions]
while gen:
children.update(gen)
gen = [sc for c in gen for sc in c.__subclasses__()]
# do something with children
# ...
After this, the children is a set containing all models that are subclasses of AppModelActions (including this model). In case you do not want to include AppModelActions, swap the two lines in the while loop.
Since you use a command, Django will first load all the models.py files from the installed apps, and so the subclasses are registered before the handle(..) function is executed, so the children set will contain all child models that are in installed apps.
Note that child-models can be abstract, so you might want to perform an extra filtering, such that only non-abstract models are used. For example post-process it with:
non_abstract_children = [c for c in children if not c._meta.abstract]

Django: How can I get the foreign key's class from a classmethod in the referred-to class?

I have the following two Django Classes MyClassA and MyClassB in two separate files. MyClassB has a foreign key reference to an instance of MyClassA. MyClassA cannot import the class MyClassB.
my_class_a/models.py:
from django.db import models
class MyClassA(models.Model):
name = models.CharField(max_length=50, null=False)
#classmethod
def my_method_a(cls):
# What do I put here to call MyClassB.my_method_b()??
my_class_b/models.py:
from my_class_a.models import MyClassA
from django.db import models
class MyClassB(models.Model):
name = models.CharField(max_length=50, null=False)
my_class_a = models.ForeignKey(MyClassA, related_name="MyClassB_my_class_a")
#staticmethod
def my_method_b():
return "Hello"
From within MyClassA's class method my_method_a, I would like to call MyClassB's static method my_method_b. How can I do it?
If my_method_a was an instance method, I would simply do self.MyClassB_my_class_a.model.my_method_b(). But since I don't have an instance of MyClassA, I don't know how to do it. I would like to take advantage of the related_name field that allows for reverse lookups of instances.
You can do it like this.
#classmethod
def my_method_a(cls):
from myclass_b.models import MyClassB
# yes, you can have an import here. and it will not
# lead to a cyclic import error
MyClassB.my_method_b()
The import failure happens only if you add the import to the top of the file. That would lead to cyclic imports one module cannot be loaded because it depends on another which depends on the other module. However when the import is inside a method the same problem does not arise.

Django - Url to dynamic model loading

So I'm trying to load a model dynamically as such :
urls.py
url(r'^list_view/([\w-]+)$', Generic_list.as_view()),
Here's my Generic_list class in views.py :
class Generic_list(ListView):
import importlib
module_name = 'models'
class_name = ?
module = __import__(module_name, globals(), locals(), class_name)
model = getattr(module, class_name)
template_name = 'django_test/generic_list.html'
About security, later I'll only allow classes from a white list.
So, what can I put in place of the question mark in order to get the class name from the url?
If you want to have your model loaded dynamically, you could do something like this:
class Generic_list(ListView):
template_name = 'django_test/generic_list.html'
#property
def model(self):
return # code that returns the actual model
More information about property. Basically, it will understand this method as an attribute of the class. Instead of having to define this attribute at the class level (meaning that the code will be evaluated when the file is imported by django), you make it look like it is an attribute, but it's acting like a method, allowing you to add business logic.
You could try to use property decorator and get_model method:
from django.views.generic import ListView
from django.apps import apps
class GenericList(ListView):
#property
def model(self):
# Obtain model name here and pass it to `get_model` below.
return apps.get_model(app_label='app_label', model_name='model_name')
Also, consider reading PEP 0008 for naming conventions.

How to Lazy Load a model in a managers to stop circular imports?

In Django you can create managers for your models. I do this by added a new file called managers.py and in my model objects = MyManager().
To stop circular imports I do self.model. However, if I need to reference a different model in my manager i.e.
from models import SecondModel
second= SecondModel(name=test).save()
self.model(second=second)
I get the following error: ImportError: cannot import name SecondModel
So is there a way in Django to lazy load a model?
The currently accepted answer is deprecated as of Django 1.7; from this answer, you can adapt your code like this.
from django.apps import apps
class SomeModelManager(...):
...
def some_function(self):
model = apps.get_model(app_label='your_app', model_name='YourModel')
You have a few options:
1. Import by name
Django has a utility function for importing by string name so you don't need to import yourself. There are several methods available for this (see this question: Django: Get model from string?)
from django.db.models.loading import get_model
class SomeModelManager(...):
...
def some_function(self):
model = get_model('your_app', 'YourModel')
object = model()
2. Imports at the bottom
Add the import at the bottom of the managers.py file and make sure to simply import the module and not the models themselves.
So...
models.py:
import managers
class SomeModel(models.Model):
...
objects = managers.SomeModelManager()
managers.py
class SomeModelManager(...):
...
def some_function(self):
object = models.SomeOtherModel()
import models

Categories