I tried to follow this tutorial
https://blog.nicolasmesa.co/posts/2018/10/saas-like-isolation-in-django-rest-framework/
using: Django 3.1 Python 3.6
Everything I did including 'The User Messages App' paragraph worked perfectly right before the 'Refactoring the views' passage
and then I got the error when running manage.py runserver
django.core.exceptions.ImproperlyConfigured: The included URLconf 'saas_django.urls' does not appear to have any pa
tterns in it. If you see valid patterns in the file then the issue is probably caused by a circular import
I tried different types of import but I can't figure out where the cirular import happens
My steps to find a bug:
the
saas_django/urls.py refers to user_messages app:
path('api/v1/user-messages/', include('user_messages.urls')),
user_messages/urls.py:
from django.urls import path
from . import views
urlpatterns = [
path('', views.UserMessageList.as_view(),
name=views.UserMessageList.name),
path('<uuid:pk>', views.UserMessageDetail.as_view(),
name=views.UserMessageDetail.name),
]
It seems like something is wrong with the imported user_messages/views.py
from rest_framework import permissions
from rest_framework import generics
from . import serializers
from .models import UserMessage
class UserMessageList(generics.ListCreateAPIView):
name = 'usermessage-list'
permission_classes = (
permissions.IsAuthenticated,
)
serializer_class = serializers.UserMessageSerializer
queryset = UserMessage.objects.all()
def perform_create(self, serializer):
user = self.request.user
company_id = user.company_id
# Added from_user
serializer.save(company_id=company_id, from_user=user)
def get_queryset(self):
# Changed this to use the UserMessageManager's method
return UserMessage.objects.get_for_user(self.request.user)
class UserMessageDetail(generics.RetrieveAPIView):
name = 'usermessage-detail'
permission_classes = (
permissions.IsAuthenticated,
)
serializer_class = serializers.UserMessageSerializer
def get_queryset(self):
# Changed this to use the UserMessageManager's method
return UserMessage.objects.get_for_user(self.request.user)
I gues the cause of the error is something about these 2 lines:
from . import serializers
from .models import UserMessage
the user_messages/serializers.py also has the import of UserMessages
from rest_framework import permissions
from rest_framework import generics
from . import serializers
from .models import UserMessage
class UserMessageList(generics.ListCreateAPIView):
name = 'usermessage-list'
permission_classes = (
permissions.IsAuthenticated,
)
serializer_class = serializers.UserMessageSerializer
queryset = UserMessage.objects.all()
def perform_create(self, serializer):
user = self.request.user
company_id = user.company_id
# Added from_user
serializer.save(company_id=company_id, from_user=user)
def get_queryset(self):
# Changed this to use the UserMessageManager's method
return UserMessage.objects.get_for_user(self.request.user)
class UserMessageDetail(generics.RetrieveAPIView):
name = 'usermessage-detail'
permission_classes = (
permissions.IsAuthenticated,
)
serializer_class = serializers.UserMessageSerializer
def get_queryset(self):
# Changed this to use the UserMessageManager's method
return UserMessage.objects.get_for_user(self.request.user)
I tried to rewrite import as:
from .models import UserMessage as UM
but it didn't work.
Project structure is:
│ db.sqlite3
│ manage.py
│ sqlite3.exe
│
├───accounts
│ admin.py
│ apps.py
│ models.py
│ serializers.py
│ tests.py
│ urls.py
│ views.py
│ __init__.py
│
├───core
│ models.py
│ serializers.py
│ views.py
│ __init__.py
│
├───saas_django
│ asgi.py
│ settings.py
│ urls.py
│ wsgi.py
│ __init__.py
│
└───user_messages
│ admin.py
│ apps.py
│ models.py
│ serializers.py
│ tests.py
│ urls.py
│ views.py
│ __init__.py
│
├───migrations
My full code is here: https://github.com/SergSm/test-django-saas/
The question is how to properly debug this type of error?
First of all there are few issues in the project itself. There are unresolved references to serializers without which no one will be able to help out. For example:
from core.serializers import CompanySafeSerializerMixin
This is referenced in a few other serializers. Also, other than this, there is one major issue in the way you have defined the models for:
class User(AbstractUser):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
company = models.ForeignKey(Company,
related_name='%(class)s',
on_delete=models.CASCADE,
editable=False,
default=get_default_company)
The default method looks like this:
def get_default_company():
"""get or create the non existent company for new users"""
return Company.objects.get_or_create(is_default_company=True,
name=DEFAULT_COMPANY)[0].pk
Not sure in what sequence you made these models but since you have not pushed your migrations onto the VCS, this will cause the issue.
Remember, always push your migrations on the VCS.
I cannot highlight this enough. It can lead to serious issues as and when the project grows.
Else, on new setup, Django will never be able to identify correct dependency between models.
Coming to the other problem, since you have used default as method that gets ref from another model, Django will not allow me to make the migrations in the first place since the model itself that its referring to is not yet made.
File "/home/dhwanil/work/packages/test-django-saas/venv/lib64/python3.6/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
django.db.utils.ProgrammingError: relation "companies" does not exist
LINE 1: ...."address", "companies"."is_default_company" FROM "companies...
^
This would have been solved if lets say you had 0001_initial one with the company model and the 0002_users where you refer the Company model. Also, you might want to look at the concept of fixtures that will help you populate a model on every fresh installation.
Coming to the main question you asked, if its indeed a circular, the best way is to closely inspect the traceback, it would show where the circular dep lies. Since I am unable to setup the project until the above mentioned issues are solved, I will not be able to give you a more directed answer.
Summary:
Always push the migrations that are created after python manage.py makemigrations on the VCS, migrations are like commits of the state of your DB, you cannot expect to work with latest state without knowing the steps it was built from.
Avoid using methods to get default from another model, the better way is always use fixtures to first populate the DB. Again, this will not be possible without splitting your migrations smartly.
return Company.objects.get_or_create(is_default_company=True,
name=DEFAULT_COMPANY)[0].pk
Alternative to use fixtures would be "Data Migrations", I always recommend to add a custom RunPython code in your migrations to assign defaults. This takes off the overhead of worrying if the data is there in the DB or not, because it will be populated the moment you run migrate.
Most circular imports can be identified by the traceback, inspect it closely.
That's all for now, if you fix the import issues, let me know. I'll be happy to take a look.
Cheers!
Related
Intro
I've read like a thousand posts on SO and other sites trying to figure out what's wrong with my Flask structure and why I can't seem to figure out something. As a last resort, I decided to finally ask the question here.
My project is really simple:
I have to fetch some data from some networking devices via API, process the data and store it in a Postgresql DB (most part of the code is in lib/).
This project is going to be deployed on multiple environments (test, dev, staging and prod).
To do the above I'm using the following:
Flask-SQLAlchemy + Flask-Migrate + Flask-Script - all of these for handling migrations and DB related operations;
Python-dotenv - for handling sensitive configuration data
Project details
My project structure looks like this:
my_project/
├── api/
├── app.py
├── config.py
├── __init__.py
├── lib/
│ ├── exceptions.py
│ └── f5_bigip.py
├── log.py
├── logs/
├── manage.py
├── migrations/
├── models/
│ ├── __init__.py
│ ├── model1.py
│ └── model2.py
└── run.py
My app.py looks like this:
import os
import sys
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from dotenv import load_dotenv
from flask import Flask
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
migrate = Migrate()
def create_app():
load_dotenv()
app = Flask(__name__)
environment = app.config['ENV']
if environment == 'production':
app.config.from_object('config.ProductionConfig')
elif environment == 'testing':
app.config.from_object('config.TestingConfig')
else:
app.config.from_object('config.DevelopmentConfig')
db.init_app(app)
migrate.init_app(app, db)
return app
My config.py looks like this:
import os
from sqlalchemy.engine.url import URL
PROJECT_ROOT = os.path.dirname(os.path.realpath(__file__))
class BaseConfig:
DEBUG = False
TESTING = False
DB_DRIVERNAME = os.getenv('DB_DRIVERNAME')
DB_HOST = os.getenv('DB_HOST')
DB_PORT = os.getenv('DB_PORT')
DB_NAME = os.getenv('DB_NAME')
DB_USERNAME = os.getenv('DB_USERNAME')
DB_PASSWORD = os.getenv('DB_PASSWORD')
DB = {
'drivername': DB_DRIVERNAME,
'host': DB_HOST,
'port': DB_PORT,
'database': DB_NAME,
'username': DB_USERNAME,
'password': DB_PASSWORD,
}
SQLALCHEMY_DATABASE_URI = URL(**DB)
SQLALCHEMY_TRACK_MODIFICATIONS = False
class DevelopmentConfig(BaseConfig):
DEVELOPMENT = True
DEBUG = True
class TestingConfig(BaseConfig):
TESTING = True
class StagingConfig(BaseConfig):
DEVELOPMENT = True
DEBUG = True
class ProductionConfig(BaseConfig):
pass
My __init__.py looks like this:
from contextlib import contextmanager
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# here, create_engine needs the SQLALCHEMY_DATABASE_URI
# how do I get it from the proper config?
engine = create_engine()
Session = sessionmaker(bind=engine)
#contextmanager
def session_scope():
"""
Provide a transactional scope around a series of operations.
"""
session = Session()
try:
yield session
session.commit()
except Exception as e:
print(f'Something went wrong here: {str(e)}. rolling back.')
session.rollback()
raise
finally:
session.close()
My manage.py looks like this:
from flask_script import Manager
from flask_migrate import MigrateCommand
from app import create_app
from models import *
manager = Manager(create_app)
manager.add_command('db', MigrateCommand)
if __name__ == '__main__':
manager.run()
My models/model1.py looks like this:
from sqlalchemy.dialects.postgresql import INET
from sqlalchemy.sql import func
from app import db
class Model1(db.Model):
__tablename__ = 'model1'
id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
ip_address = db.Column(INET, unique=True, nullable=False)
last_update = db.Column(db.DateTime(), server_default=func.now())
def __repr__(self):
return f'<Model1: {self.ip_address}>'
def __init__(self, ip_address):
self.ip_address = ip_address
Questions
Now, I have three main questions:
In my main __init__.py how can I import SQLALCHEMY_DATABASE_URI from the app's config?
Having the Session() object in the __init__.py doesn't seem too intuitive. Should it be placed in other places? For more context, the session is used in lib/f5_bigip.py and is probably going to be used in the api/ as well.
Is the overall project structure ok?
Your questions 1 and 2 are directly related to the part of your project that I found strange, so instead of answering those questions I'll just give you a much simpler and better way.
It seems in __init__.py you are implementing your own database sessions, just so that you can create a scoped session context manager. Maybe you've got that code from another project? It is not nicely integrated with the rest of your project, which uses the Flask-SQLAlchemy extension, you are just ignoring Flask-SQLAlchemy, which manages your database connections and sessions, and basically creating another connection into the database and new sessions.
What you should do instead is leverage the connections and sessions that Flask-SQLAlchemy provides. I would rewrite __init__.py as follows (doing this by memory, so excuse minor mistakes):
from contextlib import contextmanager
from app import db
#contextmanager
def session_scope():
"""
Provide a transactional scope around a series of operations.
"""
try:
yield db.session
session.commit()
except Exception as e:
print(f'Something went wrong here: {str(e)}. rolling back.')
db.session.rollback()
raise
finally:
db.session.close()
With this you are reusing the connection/sessions from Flask-SQLAlchemy. Your question 1 then is not a problem anymore. For question 2, you would use db.session anywhere in your app where you need database sessions.
Regarding question 3 I think you're mostly okay. I would suggest you do not use Flask-Script, which is a fairly old and unmaintained extension. Instead you can move your CLI to Flask's own CLI support.
I have a project structre as following,
..
├── project
│ ├── app1
│ └── app2
│ ├── app2_views
│ ├── main_view.py
│ ├── view1.py
│ ├── view2.py
i want to call classes defined in view1.py in my main_view.py
I have tried the following code,
main.py
from rest_framework import viewsets
from app2.app2_views.view1.py import class1
class mainclass(viewsets.ModelViewSet):
class1.view1_data()
view1.py
from app2.models.py import table1
from rest_framework import viewsets
from app2.serializers.py import seri1
from django.db.models import Sum
class class1(viewsets.ModelViewSet):
def view1_data():
queryset = table1.objects.values('column')
serializer_class = seri1
seri1.py
from rest_framework import serializers
from app2.models import table1
class seri1(serializers.HyperlinkedModelSerializer):
class Meta:
model = table1
fields = ['column']
but i am getting an error in main.py saying
should either include a `serializer_class` attribute, or override the `get_serializer_class()` method
since i have already included serializer class in view1.py I dont want to do it again in main_view.py.
I am new to python, what am i doing wrong here?
also i want to keep both view1.py>class1 and main.py>mainclass accessible through url(rest framework admin portal) how do i do that?
currently my urls.py is ,
from django.urls import include, path
from rest_framework import routers
from . import views
from .app2 import view1, main_view
router = routers.DefaultRouter(trailing_slash=False)
router.register(r'^view1', main_view.mainclass, basename='mis_trx_data')
router.register(r'^main_view', view1.class1, basename='mis_trx_data')
urlpatterns = [
path('', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
Thank you for your suggestions
You need to specify a class level variable called serializer_class in your ModelViewSet
class class1(viewsets.ModelViewSet):
serializer_class = MySerializer
def view1_data():
queryset = table1.objects.values('column')
serializer = self.serializer_class(request.data)
Ajax request to send the form data to API
$.ajax({
url: api-end-point,
data: JSON.stringify($("#form").serialize()),
success: function (E){
console.log("sucess");
},
error: function(e){
console.log("error");
}
});
I am trying to implement signals for my app and my directory structure looks like this
- src
- utility_apps
__init__.py
- posts
- migrations
- static
- templates
admin.py
views.py
apps.py
signals.py
models.py
__init__.py
......
static
manage.py
......
Here posts is my app and the signals.py is inside its folder, But my signals aren't working.
I have defined my signal code as -
from .models import Post
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=Post)
def give_group_owner_permission(sender, instance, created, **kwargs):
print("coming to signal")
if created:
print("created")
But it doesn't work. In my apps.py I have changed the ready function as
class Post(AppConfig):
name = 'post'
def ready(self):
import utility_apps.posts.signals
I have even tried importing posts.signal in the ready function. What I am doing wrong here, please help
My installed apps look like below
INSTALLED_APPS = [
'utility_apps.posts',
'mainapp',
.....
]
The following solution worked for me.
The first change which I had to make was put a default_app_config value in my __init__.py file of my posts app as
default_app_config = 'utility_apps.posts.apps.PostsConfig'
And then I had to change PostsConfig class as
class PostsConfig(AppConfig):
name = 'utility_apps.posts'
def ready(self):
import utility_apps.posts.signals
Basically I had to change two things -
The name which was set to posts by default
Change the ready function and import my signals in it
It worked for me.
Alternatively, I could have also included my PostsConfig in my installed app.
What is the proper way to say what is my AUTH_USER_MODEL?
I have the following set:
Folder structure:
--- backend
----- api
-------- models
----------- user.py
user.py lies within models folder
in settings.py:
AUTH_USER_MODEL = 'myapp.User'
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'api',
]
The model:
class User:
class Meta:
app_label = "myapp"
And when I run any manage.py command, for example
python manage.py showmigrations
I get this error:
LookupError: No installed app with label 'myapp'.
The problem solution would be renaming api folder to myapp, which I cannot do due to some restrictions on model names. Or setting AUTH_USER_MODEL to api.User, but this will incur changing all data table names and those must remain the same
Data table names starting with 'myapp_' should not change
Given your folder structure looks like that:
backend
└── api
| ├── __init__.py
| ├── apps.py
| └── models
| ├── __init__.py
| └── user.py
├── manage.py
└── settings.py
then you can create an app config in backend/api/ by creating a file called apps.py inside it. There you can rename your app:
backend/api/apps.py:
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = 'myapp'
verbose_name = _('My App')
Also you need to add this to the __init__.py inside that folder:
backend/api/__init__.py:
default_app_config = 'api.apps.MyAppConfig'
Also if you want to use myapp.User as your user model you also have to import in in the models module:
backend/api/models/__init__.py:
from .user import User
# or use "from .user import *" to import everything but then make sure you have __all__ defined in user.py
The solution was to change AppConfig.label in class MyAppConfig(AppConfig), so if you update your answer, I will accept it. Changing name ends in nothing, it must be the real name of the module the code sits in, but adding label = 'arbitrary_name' is actually the solution
I have both versions python 2.7 and 3.4 installed. I am using some code which is developed under 2.7 but I am using under 3.4. So after compiling using the following command
python manage.py runserver
I get the following error -
File "C:\pyprojects\focus\site\general\forms.py", line 26, in Meta
model = models.UserProfile
AttributeError: 'module' object has no attribute 'UserProfile'
The directory structure is
├───focus
│ ├───data_dumps
│ ├───notes
│ ├───setup
│ └───site(main project folder)
| └───static
| └───general
| +--forms.py
| +--models.py
| └───pro
| └───models
| +--__init__.py
| +--plans.py
│ └───focus2
│ └───templates
| +--__init__.py
│ +--settings.py
│ +--util.py
│ +--wsgi.py
│ +--manage.py
As models.py and forms.py is under same directory(general) so I have imported the model in forms.py in this way
from .models import models
Now in models.py I have defined the class
class UserProfile(models.Model, HashedPk):
user = models.OneToOneField(User, unique=True)
is_pro = models.BooleanField(default=False, blank=True)
......................................................
In forms.py the code is
class UserProfileForm(forms.ModelForm):
class Meta:
model = models.UserProfile
.....................
Is there any special way to call the model in python 3.4.
Any help is highly appreciated.
It's sorted. The import needs to be
from . import models
instead
from .models import models