mmcardle / django_builder

Django Builder Site
MIT License
608 stars 124 forks source link

Django "AbstractModel" gets registered with admin, causing failure #194

Closed GeoMaciolek closed 1 year ago

GeoMaciolek commented 1 year ago

Overview

django_builder creates an entry in admin.py for abstract models. (It apparently shouldn't?)

Test Environment

Tested & verified on:

Issue

Steps

Expected Behavior

Actual Behavior

Project fails to run manage.py - for example with the following: django.core.exceptions.ImproperlyConfigured: The model abstractmodel1 is abstract, so it cannot be registered with admin.

Example

models.py ```python from django.db import models from django.urls import reverse class model1(models.Model): # Fields name = models.TextField(max_length=100) created = models.DateTimeField(auto_now_add=True, editable=False) last_updated = models.DateTimeField(auto_now=True, editable=False) class Meta: pass def __str__(self): return str(self.name) def get_absolute_url(self): return reverse("app1_model1_detail", args=(self.pk,)) def get_update_url(self): return reverse("app1_model1_update", args=(self.pk,)) @staticmethod def get_htmx_create_url(): return reverse("app1_model1_htmx_create") def get_htmx_delete_url(self): return reverse("app1_model1_htmx_delete", args=(self.pk,)) class model2(models.Model): # Relationships somrelationship = models.ManyToManyField("app1.model1", related_name='mym2') # Fields created = models.DateTimeField(auto_now_add=True, editable=False) last_updated = models.DateTimeField(auto_now=True, editable=False) description = models.TextField(max_length=100) class Meta: pass def __str__(self): return str(self.pk) def get_absolute_url(self): return reverse("app1_model2_detail", args=(self.pk,)) def get_update_url(self): return reverse("app1_model2_update", args=(self.pk,)) @staticmethod def get_htmx_create_url(): return reverse("app1_model2_htmx_create") def get_htmx_delete_url(self): return reverse("app1_model2_htmx_delete", args=(self.pk,)) class abstractmodel1(models.Model): # Fields last_updated = models.DateTimeField(auto_now=True, editable=False) created = models.DateTimeField(auto_now_add=True, editable=False) class Meta: abstract = True def __str__(self): return str(self.pk) def get_absolute_url(self): return reverse("app1_abstractmodel1_detail", args=(self.pk,)) def get_update_url(self): return reverse("app1_abstractmodel1_update", args=(self.pk,)) @staticmethod def get_htmx_create_url(): return reverse("app1_abstractmodel1_htmx_create") def get_htmx_delete_url(self): return reverse("app1_abstractmodel1_htmx_delete", args=(self.pk,)) class child1absmodel1(abstractmodel1): # Fields created = models.DateTimeField(auto_now_add=True, editable=False) last_updated = models.DateTimeField(auto_now=True, editable=False) class Meta: pass def __str__(self): return str(self.pk) def get_absolute_url(self): return reverse("app1_child1absmodel1_detail", args=(self.pk,)) def get_update_url(self): return reverse("app1_child1absmodel1_update", args=(self.pk,)) @staticmethod def get_htmx_create_url(): return reverse("app1_child1absmodel1_htmx_create") def get_htmx_delete_url(self): return reverse("app1_child1absmodel1_htmx_delete", args=(self.pk,)) ```
admin.py ```python from django.contrib import admin from django import forms from . import models class model1AdminForm(forms.ModelForm): class Meta: model = models.model1 fields = "__all__" class model1Admin(admin.ModelAdmin): form = model1AdminForm list_display = [ "name", "created", "last_updated", ] readonly_fields = [ "name", "created", "last_updated", ] class model2AdminForm(forms.ModelForm): class Meta: model = models.model2 fields = "__all__" class model2Admin(admin.ModelAdmin): form = model2AdminForm list_display = [ "created", "last_updated", "description", ] readonly_fields = [ "created", "last_updated", "description", ] class abstractmodel1AdminForm(forms.ModelForm): class Meta: model = models.abstractmodel1 fields = "__all__" class abstractmodel1Admin(admin.ModelAdmin): form = abstractmodel1AdminForm list_display = [ "last_updated", "created", ] readonly_fields = [ "last_updated", "created", ] class child1absmodel1AdminForm(forms.ModelForm): class Meta: model = models.child1absmodel1 fields = "__all__" class child1absmodel1Admin(admin.ModelAdmin): form = child1absmodel1AdminForm list_display = [ "created", "last_updated", ] readonly_fields = [ "created", "last_updated", ] admin.site.register(models.model1, model1Admin) admin.site.register(models.model2, model2Admin) admin.site.register(models.abstractmodel1, abstractmodel1Admin) admin.site.register(models.child1absmodel1, child1absmodel1Admin) ```

Output

File "/home/geo/programming/repos/django-music-and-creative-project-tracking/creative_proj_mgr_htmx/simpletest/app1/admin.py", line 89, in <module>
    admin.site.register(models.abstractmodel1, abstractmodel1Admin)
  File "/home/geo/.local/share/virtualenvs/django-music-and-creative-project-tracking-di6leSv5/lib/python3.8/site-packages/django/contrib/admin/sites.py", line 119, in register
    raise ImproperlyConfigured(
django.core.exceptions.ImproperlyConfigured: The model abstractmodel1 is abstract, so it cannot be registered with admin.
mmcardle commented 1 year ago

Should be fixed now

GeoMaciolek commented 1 year ago

Has this been deployed to djangobuilder.io? If so, I'm still having issues (shown below).

I haven't yet managed to get this running locally (some sort of firebase issue, not surprising given I've literally never used it), so I can't test the recent commit. Will keep trying, though!

Error as it stands (truncated):

  File "/home/geo/simplertest/simplerapp/urls.py", line 4, in <module>
    from . import api
  File "/home/geo/simplertest/simplerapp/api.py", line 23, in <module>
    class simplerabstractViewSet(viewsets.ModelViewSet):
  File "/home/geo/simplerapp/api.py", line 26, in simplerabstractViewSet
    queryset = models.simplerabstract.objects.all()
AttributeError: type object 'simplerabstract' has no attribute 'objects'
Full traceback ``` Traceback (most recent call last): File "manage.py", line 15, in execute_from_command_line(sys.argv) File "/home/geo/.local/share/virtualenvs/django-music-and-creative-project-tracking-di6leSv5/lib/python3.8/site-packages/django/core/management/__init__.py", line 442, in execute_from_command_line utility.execute() File "/home/geo/.local/share/virtualenvs/django-music-and-creative-project-tracking-di6leSv5/lib/python3.8/site-packages/django/core/management/__init__.py", line 436, in execute self.fetch_command(subcommand).run_from_argv(self.argv) File "/home/geo/.local/share/virtualenvs/django-music-and-creative-project-tracking-di6leSv5/lib/python3.8/site-packages/django/core/management/base.py", line 412, in run_from_argv self.execute(*args, **cmd_options) File "/home/geo/.local/share/virtualenvs/django-music-and-creative-project-tracking-di6leSv5/lib/python3.8/site-packages/django/core/management/base.py", line 458, in execute output = self.handle(*args, **options) File "/home/geo/.local/share/virtualenvs/django-music-and-creative-project-tracking-di6leSv5/lib/python3.8/site-packages/django/core/management/commands/check.py", line 76, in handle self.check( File "/home/geo/.local/share/virtualenvs/django-music-and-creative-project-tracking-di6leSv5/lib/python3.8/site-packages/django/core/management/base.py", line 485, in check all_issues = checks.run_checks( File "/home/geo/.local/share/virtualenvs/django-music-and-creative-project-tracking-di6leSv5/lib/python3.8/site-packages/django/core/checks/registry.py", line 88, in run_checks new_errors = check(app_configs=app_configs, databases=databases) File "/home/geo/.local/share/virtualenvs/django-music-and-creative-project-tracking-di6leSv5/lib/python3.8/site-packages/django/core/checks/urls.py", line 14, in check_url_config return check_resolver(resolver) File "/home/geo/.local/share/virtualenvs/django-music-and-creative-project-tracking-di6leSv5/lib/python3.8/site-packages/django/core/checks/urls.py", line 24, in check_resolver return check_method() File "/home/geo/.local/share/virtualenvs/django-music-and-creative-project-tracking-di6leSv5/lib/python3.8/site-packages/django/urls/resolvers.py", line 494, in check for pattern in self.url_patterns: File "/home/geo/.local/share/virtualenvs/django-music-and-creative-project-tracking-di6leSv5/lib/python3.8/site-packages/django/utils/functional.py", line 57, in __get__ res = instance.__dict__[self.name] = self.func(instance) File "/home/geo/.local/share/virtualenvs/django-music-and-creative-project-tracking-di6leSv5/lib/python3.8/site-packages/django/urls/resolvers.py", line 715, in url_patterns patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module) File "/home/geo/.local/share/virtualenvs/django-music-and-creative-project-tracking-di6leSv5/lib/python3.8/site-packages/django/utils/functional.py", line 57, in __get__ res = instance.__dict__[self.name] = self.func(instance) File "/home/geo/.local/share/virtualenvs/django-music-and-creative-project-tracking-di6leSv5/lib/python3.8/site-packages/django/urls/resolvers.py", line 708, in urlconf_module return import_module(self.urlconf_name) File "/usr/lib/python3.8/importlib/__init__.py", line 127, in import_module return _bootstrap._gcd_import(name[level:], package, level) File "", line 1014, in _gcd_import File "", line 991, in _find_and_load File "", line 975, in _find_and_load_unlocked File "", line 671, in _load_unlocked File "", line 848, in exec_module File "", line 219, in _call_with_frames_removed File "/home/geo/programming/repos/django-music-and-creative-project-tracking/simplertest/simplertest/urls.py", line 24, in path('simplerapp/', include('simplerapp.urls')), File "/home/geo/.local/share/virtualenvs/django-music-and-creative-project-tracking-di6leSv5/lib/python3.8/site-packages/django/urls/conf.py", line 38, in include urlconf_module = import_module(urlconf_module) File "/usr/lib/python3.8/importlib/__init__.py", line 127, in import_module return _bootstrap._gcd_import(name[level:], package, level) File "", line 1014, in _gcd_import File "", line 991, in _find_and_load File "", line 975, in _find_and_load_unlocked File "", line 671, in _load_unlocked File "", line 848, in exec_module File "", line 219, in _call_with_frames_removed File "/home/geo/programming/repos/django-music-and-creative-project-tracking/simplertest/simplerapp/urls.py", line 4, in from . import api File "/home/geo/programming/repos/django-music-and-creative-project-tracking/simplertest/simplerapp/api.py", line 23, in class simplerabstractViewSet(viewsets.ModelViewSet): File "/home/geo/programming/repos/django-music-and-creative-project-tracking/simplertest/simplerapp/api.py", line 26, in simplerabstractViewSet queryset = models.simplerabstract.objects.all() AttributeError: type object 'simplerabstract' has no attribute 'objects' ```
GeoMaciolek commented 1 year ago

Looks like one or two other places this exists. I may try to submit a pull request on this, but given I can't test as of yet, I'm reluctant to do so! Example, in how api.py is generated by rendering.js:

  api_py(projectid, appid) {
    let api = 'from rest_framework import viewsets, permissions\n'
    api += '\n'
--- old-rendering.js    2023-06-11 23:30:25.710385735 -0400
+++ src/django/rendering.js     2023-06-11 23:10:08.810616020 -0400
@@ -965,7 +965,7 @@
     api += 'from . import serializers\n'
     api += 'from . import models\n'

-    const models = this.get_models(appid);
+    const models = this.get_models(appid).filter(model => !model.abstract);

     models.forEach((model) => {
      api += '\n\n'
      api += 'class ' + model.name + 'ViewSet(viewsets.ModelViewSet):\n'
      api += '    """ViewSet for the ' + model.name + ' class"""\n'
      api += '\n'
      api += '    queryset = models.' + model.name + '.objects.all()\n'
      api += '    serializer_class = serializers.' + model.name + 'Serializer\n'
      api += '    permission_classes = [permissions.IsAuthenticated]\n'
    })
    return api
  }