django models cyclic import - python

I have read some relevant posts on the issue on mutual imports (circular). I am working on a django app, here's how I run to an issue:
I have two apps, first one articles, second one tags
my article model have a many to many field, to indicate relevant tags:
articles/models.py
from django.db import models
from tags.models import Tag
class Article(models.Model):
tags = models.ManyToManyField(Tag)
however, in my tags app, I also need to import Article model to achieve the methods:
tags/models.py
from django.db import models
from articles.models import Article
# Create your models here.
class Tag(models.Model):
title = models.CharField(max_length=100)
content = models.CharField(max_length=255)
def getAritcleLength():
pass
def getQuestionLength():
pass
I usually use a module to combine those class definitions, normally won't run into issue according to method resolution order. However, in django the workflow we need to put classes into separated folders, I will be so glad for any suggestion.

Don't import the Tag model in the Article model, but use the string reference of the class instead.
# articles/models.py
from django.db import models
class Article(models.Model):
tags = models.ManyToManyField('Tag') OR: you can use app_name.model_name format as well
Another way is to use Django's method to get the model from a string and then use that variable in place for the model reference.
Django: Get model from string?

Try to delete this string
from articles.models import Article

Related

Circular importing error using wagtail-generic-chooser

I am using wagtails wagtail-generic-chooser to create customChoosers for my data models and it is working great whenever I am referencing other modelAdmin models.
However, I have come across a situation where I have a Lexis model with a field that has a FK link to itself. The idea is to have a Lexis term, and then there can be related lexis terms connected to it. It works fine with a normal FieldPanel but this isn't a very good UI experience when there are hundreds of lexis terms. Accordingly, I wanted to create a custom LexisChooser for this field. However, the issue I've run into is according to the documentation in order to create a functional widget, I am required to create both a view and adminChooser that references the model the ChooserPanel is connected to.
https://github.com/wagtail/wagtail-generic-chooser#chooser-widgets-model-based
This makes sense, however, when I then try to import my LexisChooser into my Lexis model to use the LexisChooser as a widget, I get the error below.
ImportError: cannot import name 'Lexis' from 'lexis.models'
I realize this is due to a circular import error issue because I have subclasses that are importing the Lexis Class in order to build the LexisChooser widget and then I am trying to import that widget into the Lexis Class.
I know this isn't a bug with Wagtail nor is it a problem with wagtail-generic-chooser, However, does anyone have any idea how I can refactor the code to make this function so that I can use a LexisChooser on a field of the Lexis Model.
Below is my code.
views.py create a view
from django.utils.translation import ugettext_lazy as _
from generic_chooser.views import ModelChooserViewSet
from lexis.models import Lexis
class LexisChooserViewSet(ModelChooserViewSet):
icon = 'user'
model = Lexis
page_title = _("Choose A Lexis Term")
per_page = 20
order_by = 'term'
fields = ['term']
wagtail_hooks.py register view
from wagtail.core import hooks
from .views import LexisChooserViewSet
#hooks.register('register_admin_viewset')
def register_lexis_chooser_viewset():
return LexisChooserViewSet('lexis_chooser', url_prefix='lexis-chooser')
widgets.py create a widget
from django.utils.translation import ugettext_lazy as _
from generic_chooser.widgets import AdminChooser
from lexis.models import Lexis
class LexisChooser(AdminChooser):
choose_one_text = _('Choose a Lexis')
choose_another_text = _('Choose another Lexis')
link_to_chosen_text = _('Edit this Lexis')
model = Lexis
choose_modal_url_name = 'lexis_chooser:choose'
lexis/models.py use widget
from django.db import models
from modelcluster.fields import ParentalKey
from wagtail.admin.edit_handlers import FieldPanel, InlinePanel
from wagtail.core.models import Orderable
from modelcluster.models import ClusterableModel
from chooser_panels.widgets import LexisChooser
# Orderable link to multiple other linked lexis terms
class LexisLink(Orderable):
page = ParentalKey("lexis.Lexis", related_name="lexis_link")
term_link = models.ForeignKey(
'lexis.Lexis',
on_delete=models.SET_NULL,
related_name='term_linked',
null=True
)
panels = [
FieldPanel("term_link", widget=LexisChooser)
]
class Lexis(ClusterableModel):
template = "lexis/lexis_page.html"
term = models.CharField(max_length=100, blank=True, null=True)
panels = [
FieldPanel("term"),
InlinePanel('lexis_link', label='Linked Lexis Terms'),
]
def __str__(self):
return self.term
class Meta:
verbose_name = "Lexis"
verbose_name_plural = "Lexis"
This, unfortunately, results in a circular import error:
ImportError: cannot import name 'Lexis' from 'lexis.models'
On researching this error, I found that people recommend importing Lexis within the class as required rather than at the top of each file, but that doesn't seem to work with the subclassing as outlined above, because I get the same error.
If you have any ideas on how I can refactor the code to make it work and not create the circular import error it would be much appreciated.
I am running
Django 3,
python 3.7,
wagtail 2.8
Thank you
Split your models file into two separate files containing Lexis and LexisLink, as demonstrated in the documentation.
Then LexisLink can refer to LexisChooser while it cleanly refers to the Lexis model.

Django deep admin.TabularInline

if there is relation like:
B.ForeignKey(A)
django can show it in one admin page by TabularInline:
admin.py
from django.contrib import admin
from myapp2 import models
# Register your models here.
class TabularInlineB(admin.TabularInline):
model=models.B
class AdminA(admin.ModelAdmin):
inlines=[TabularInlineB, ]
admin.site.register(models.A, AdminA)
models.py
from django.db import models
# Create your models here.
class A(models.Model):
name=models.CharField(max_length=10)
class B(models.Model):
name=models.CharField(max_length=10)
a=models.ForeignKey(A)
.the output is like:
but if we add another ForeignKey relation to B like below,
B.ForeignKey(A)
C.ForeignKey(B)
then How I can show all models in one admin page?
admin.py
from django.contrib import admin
from myapp2 import models
class TabularInlineC(admin.TabularInline):
model=models.C
class TabularInlineB(admin.TabularInline):
model=models.B
inlines=[TabularInlineC, ]
class AdminA(admin.ModelAdmin):
inlines=[TabularInlineB, ]
admin.site.register(models.A, AdminA)
.models.py
from django.db import models
# Create your models here.
class A(models.Model):
name=models.CharField(max_length=10)
class B(models.Model):
name=models.CharField(max_length=10)
a=models.ForeignKey(A)
class C(models.Model):
b=models.ForeignKey(B)
name=models.CharField(max_length=10)
the output dose not show C:
I had the same issue and I managed to do it. I don't know if it's exactly what you need but let me know if it's not I'll remove my answer.
You can do it like that:
Admin.py
from django.contrib import admin
from myapp2 import models
class TabularInlineC(admin.TabularInline):
model=models.C
class TabularInlineB(admin.TabularInline):
model=models.B
class AdminA(admin.ModelAdmin):
inlines=[TabularInlineB, TabularInlineC ]
admin.site.register(models.A, AdminA)
That will render two inlines having each a section in the admin page of model A. Note that in this code, both model B and Model C have foreign key to model A. Indeed, this is not exactly the same pattern you are using in your question but it is, in my opinion, the simplest way of achieving what you want. The fact that you point two models to the same, allow you to consider this Model has the parent model. So, if you can find a common field to point to, you will be able to add two inlines in the same form since both models will have a link to Model A.
Also, another interesting thing you can do is add classes = ['collapse'] to both class TabularInlineB and class TabularInlineC this will allow collapsable on those two sections of your admin page.
Hope it helps !
EDIT
If you absolutely need to render only one inline that include both models, I'm also not sure if it is possible out of the box with Django.

Is there a way to use Wagtail Snippets to display existing Django Models?

With Wagtail CMS, what is the best way to mimic the "Plugin" functionality of Django CMS?
In Django CMS I am able to write a custom plugin that can display a template and any related information to that model. Content Managers can then add that plugin to a placeholder anywhere on the site.
With Wagtail, the closest thing I can find is the Snippet, but each time you use the Snippet you have to include it specifically in the Page model.
Take these two models for example:
class Pet(models.Model):
species = models.CharField(max_length=10)
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=100)
For the content manager to be able to access these I'd need to register them as snippets, and then list each model in that page's model like so:
class HomePage(Page):
content_panels = Page.content_panels + [
SnippetChooserPanel('pet'),
SnippetChooserPanel('book'),
]
Is there a better way to do this with Wagtail?
To achieve this kind of solution without defining models you could look at the StreamField approach.
You could define a custom StreamField block type that models pets or books. Make this available on the pages that need this, you still have to be explicit about which pages can use this StreamField though.
You can then define a custom template that renders these items, available in the documentation:
http://docs.wagtail.io/en/v1.12.1/topics/streamfield.html#template-rendering
To achieve this kind of solution with a generic defined template you could define a method on both the Book and Pets classes.
Something like:
from django.template.loader import render_to_string
from django.utils.safestring import mark_safe
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=100)
template = 'path/to/template.html'
get_as_html(self):
return mark_safe(render_to_string(self.template, {'self': self}))
You would need to create the template file that handles the Book instance. This means that you can easily call {{ Book.get_has_html }} within any templates that use the Book snippet.

How to Model a Foreign Key in a Reusable Django App?

In my django site I have two apps, blog and links. blog has a model blogpost, and links has a model link. There should be a one to many relationship between these two things. There are many links per blogpost, but each link has one and only one blog post. The simple answer is to put a ForeignKey to blogpost in the link model.
That's all well and good, however there is a problem. I want to make the links app reusable. I don't want it to depend upon the blog app. I want to be able to use it again in other sites and perhaps associate links with other non-blogpost apps and models.
A generic foreign key seems like it might be the answer, but not really. I don't want links to be able to associate with any model in my site. Just the one that I explicitly specify. And I know from prior experience that there can be issues using generic foreign keys in terms of database usage because you can't do a select_related over a generic foreign key the way you can with a regular foreign key.
What is the "correct" way to model this relationship?
If you think the link app will always point to a single app then one approach would be to pass the name of the foreign model as a string containing the application label instead of a class reference (Django docs explanation).
In other words, instead of:
class Link(models.Model):
blog_post = models.ForeignKey(BlogPost)
do:
from django.conf import setings
class Link(models.Model):
link_model = models.ForeignKey(settings.LINK_MODEL)
and in your settings.py:
LINK_MODEL = 'someproject.somemodel'
I think TokenMacGuy is on the right track. I would look at how django-tagging handles a similar generic relationship using the content type, generic object_id, and generic.py. From models.py
class TaggedItem(models.Model):
"""
Holds the relationship between a tag and the item being tagged.
"""
tag = models.ForeignKey(Tag, verbose_name=_('tag'), related_name='items')
content_type = models.ForeignKey(ContentType, verbose_name=_('content type'))
object_id = models.PositiveIntegerField(_('object id'), db_index=True)
object = generic.GenericForeignKey('content_type', 'object_id')
objects = TaggedItemManager()
class Meta:
# Enforce unique tag association per object
unique_together = (('tag', 'content_type', 'object_id'),)
verbose_name = _('tagged item')
verbose_name_plural = _('tagged items')
Anoher way to solve this is how django-mptt does this: define only an abstract model in a reusable app(MPTTModel), and require to inherit it with defining some fields (parent=ForeignKey to self, or whatever your app usecase will require)
Probably you need to use the content types app to link to a model. You might then arrange for your app to check the settings to do some additional checking to limit which content types it will accept or suggest.
I'd go with generic relations. You can do something like select_related, it just require some extra work. But I think it's worth it.
One possible solution for generic select_related-like functionality:
http://bitbucket.org/kmike/django-generic-images/src/tip/generic_utils/managers.py
(look at GenericInjector manager and it's inject_to method)
This question and Van Gale's answer lead me to the question, how it could be possible, to limit contenttypes for GFK without the need of defining it via Q objects in the model, so it could be completly reuseable
the solution is based on
django.db.models.get_model
and the eval built-in, that evaluates a Q-Object from settings.TAGGING_ALLOWED. This is necessary for usage in the admin-interface
My code is quite rough and not fully tested
settings.py
TAGGING_ALLOWED=('myapp.modela', 'myapp.modelb')
models.py:
from django.db import models
from django.db.models import Q
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from django.db.models import get_model
from django.conf import settings as s
from django.db import IntegrityError
TAGABLE = [get_model(i.split('.')[0],i.split('.')[1])
for i in s.TAGGING_ALLOWED if type(i) is type('')]
print TAGABLE
TAGABLE_Q = eval( '|'.join(
["Q(name='%s', app_label='%s')"%(
i.split('.')[1],i.split('.')[0]) for i in s.TAGGING_ALLOWED
]
))
class TaggedItem(models.Model):
content_type = models.ForeignKey(ContentType,
limit_choices_to = TAGABLE_Q)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
def save(self, force_insert=False, force_update=False):
if self.content_object and not type(
self.content_object) in TAGABLE:
raise IntegrityError(
'ContentType %s not allowed'%(
type(kwargs['instance'].content_object)))
super(TaggedItem,self).save(force_insert, force_update)
from django.db.models.signals import post_init
def post_init_action(sender, **kwargs):
if kwargs['instance'].content_object and not type(
kwargs['instance'].content_object) in TAGABLE:
raise IntegrityError(
'ContentType %s not allowed'%(
type(kwargs['instance'].content_object)))
post_init.connect(post_init_action, sender= TaggedItem)
Of course the limitations of the contenttype-framework affect this solution
# This will fail
>>> TaggedItem.objects.filter(content_object=a)
# This will also fail
>>> TaggedItem.objects.get(content_object=a)

Django how to filter queryset by a subclass using django model utils?

I am using django-model-utils for inheritance Managers. I want to get results of only one subclass at a time.
managers.py
from model_utils.managers import InheritanceManager
class PostManager(InheritanceManager):
pass
models.py
from .managers import PostManager
class Post(models.Model):
title = models.CharField(max_length=20)
text = models.TextField()
objects = PostManager()
class ImagePost(Post, models.Model):
source = models.URLField()
image = models.ImageField(upload_to="images/%Y/%m/%d")
class VideoPost(Post, models.Model):
source = models.URLField()
I want to return results of only image type. by writing a simpler query like this.
Post.objects.filter(type='image').select_subclasses()
What i have tried:
if type == 'image':
Post.objects.filter(imagepost__isnull=False).select_subclasses()
This works but is kind of anti-pattern, i don't want to write conditions in views for every content type.
Is there better way like defining a property in models or converting it into a manager method? or am i missing something?
Have you tried to pass the class to select_subclasses method?
Post.objects.select_subclasses(ImagePost)
Check their doc about this feature.
Edit:
I misunderstood the question, but sounds like OP wants only the Post with type ImagePost. Doing select_subclasses(ImagePost) would fetch everything and convert the objects with type ImagePost to ImagePost instances. The solution should be as simple as :
image_posts = ImagePost.objects.all()

Categories

Resources