This is my structure.
│ run.py
│
├─admin
│ │ views.py
│ │ views.pyc
│ │ __init__.py
│ │ __init__.pyc
│ │
│ └─templates
│ hello.html
│ index.html
│
└─main
│ views.py
│ views.pyc
│ __init__.py
│ __init__.pyc
│
└─templates
index.html
I try to build two apps in flask project. My purpose is when I browse http://127.0.0.1:5000/main/ it will returns main/templates/index.html, when I browse http://127.0.0.1:5000/admin/ it will returns main/templates/index.html.
I find I browse http://127.0.0.1:5000/main/ it will return main/templates/index.html. WTF?
If I rename html of different directory when changing main/templates/index.html to main/templates/index2.html, I am valid to get main/templates/index2.html in my expectation
admin/views.py:
from flask import Blueprint, render_template
admin = Blueprint("admin", __name__, template_folder="templates")
#admin.route('/')
def index():
return render_template('index.html')
main/views.py:
from flask import Blueprint, render_template
main = Blueprint("main", __name__, template_folder="templates")
#main.route('/')
def index():
return render_template('index.html')
run.py
import os
from flask import Flask
from main.views import main
from admin.views import admin
app = Flask(__name__)
app.register_blueprint(main, url_prefix='/main')
app.register_blueprint(admin, url_prefix='/admin')
print app.url_map
app.run(debug=True)
The issue is same in django. The document has recommended your template directory should set: app_name/templates/app_name.
Related
I just created a signal to generate a token when the user is created, I use tortoise.signals to generate it in a separate file, my project structure is like this
├── Makefile
├── README.md
├── core
│ ├── __init__.py
│ ├── authentication.py
│ ├── email.py
│ ├── models.py
│ ├── router.py
│ ├── schema.py
│ ├── signals.py
│ └── views.py
├── main.py
├── requirements.txt
├── static
└── templates
└── verification.html
And here is my signal file
# signals.py
from tortoise.signals import post_save
from tortoise import BaseDBAsyncClient
from typing import Type, List, Optional
from .models import UserModel, business_pydantic
from .email import send_email
#post_save(sender=UserModel)
async def create_business(
sender: "Type[UserModel]",
instance: UserModel,
created: bool,
using_db: Optional[BaseDBAsyncClient],
update_fields: List[str],
) -> None:
if created:
business_objects: UserModel = await UserModel.create(
business_name = instance.username, owner_id = instance
)
await business_pydantic.from_tortoise_orm(business_objects)
# send email
await send_email([instance.email], instance)
and I import the module in function create_app
from dotenv import dotenv_values
from fastapi import FastAPI
from tortoise.contrib.fastapi import register_tortoise
from fastapi.staticfiles import StaticFiles
from .router import router
def create_app():
app = FastAPI()
# mount static files
app.mount("/static", StaticFiles(directory="static"), name="static")
import signals # <- import my signal file here
register_tortoise(
app=app,
db_url="postgres://postgres:1234#localhost:5432/ecommerce_db",
modules={"models": ["core.models"]},
generate_schemas=True,
add_exception_handlers=True,
)
# add routes
app.include_router(router)
return app
My problem is that the signal that I created in the signals.py file is not read/executed, how do I make the functions that I make executed properly if I make them in a separate file, is there a way to register so that the signal is read in fast API?
Thanks!
I have an API that uses FastAPI. In a single file (main.py), I have the call to the function that creates the API
from fastapi import FastAPI
# ...
app = FastAPI()
As well as all the endpoints:
#app.post("/sum")
async def sum_two_numbers(number1: int, number2: int):
return {'result': number1 + number2}
But as the application gets larger, the file is becoming messy and hard to maintain. The obvious solution would be to keep function definitions in separate modules and just import them and use them in main.py, like this:
from mymodules.operations import sum_two_numbers
# ...
#app.post("/sum")
sum_two_numbers(number1: int, number2: int)
Only that doesn't work. I don't know if I'm doing it wrong or it can't be done, but I get this error from VSCode:
Expected function or class declaration after decorator | Pylance
(My program has so many errors that I haven't seen the actual interpreter complaint, but if that's important, I can try debug it and post it here)
So is this impossible to do and I have to stick to the one-file API, or it is possible to do, but I'm doing it wrong? If the second, what is the right way?
The common solution is to split your application into subrouters. Instead of using app directly when registering your views, you create an instance APIRouter (from fastapi import APIRouter) inside each of your modules, then you register these subrouters into your main application.
Inside a dedicated api module, such as api/pages.py:
from fastapi import APIRouter
router = APIRouter()
#router.get('')
async def get_pages():
return ...
from .api import (
pages,
posts,
users,
)
app.include_router(pages.router, prefix='/pages')
app.include_router(posts.router, prefix='/posts')
app.include_router(users.router, prefix='/users')
Another powerful construct you can use is to have two dedicated base routers, one that requires authentication and one that doesn't:
unauthenticated_router = APIRouter()
authenticated_router = APIRouter(dependencies=[Depends(get_authenticated_user)])
.. and you can then register the different routes under each router, depending on whether you want to guard the route with an authenticated user or not. You'd have two subrouters inside each module, one for endpoints that require authentication and one for those that doesn't, and name them appropriately (and if you have no public endpoints, just use authenticated_router as the single name).
unauthenticated_router.include_router(authentication.router, prefix='/authenticate')
unauthenticated_router.include_router(users.unauthenticated_router, prefix='/users', tags=['users'])
authenticated_router.include_router(users.router, prefix='/users')
Any sub router registered under authenticated_router will have the get_authenticated_user dependency evaluated first, which in this case would throw a 401 error if the user wasn't logged in. You can then authorized further based on roles etc. in the dependencies for the view function - but this makes it very explicit whether you want your endpoint to end up in a chain that requires authentication or not.
So is this impossible to do and I have to stick to the one-file API, or it is possible to do, but I'm doing it wrong?
One file api is just for demo/test purposes, in the real world you always do multi-file api especialy with framework like fastapi where you use different type of file like pydantic models, db models, etc.
If the second, what is the right way?
There is no "right way", there is ways that fit your needs.
You can follow the advenced user guide, to see a good example.
what the doc suggest:
.
├── app
│ ├── __init__.py
│ ├── main.py
│ ├── dependencies.py
│ └── routers
│ │ ├── __init__.py
│ │ ├── items.py
│ │ └── users.py
│ └── internal
│ ├── __init__.py
│ └── admin.py
what i use when i have db, pydantic models, etc
.
├── app
│ ├── __init__.py
│ ├── main.py
│ ├── dependencies.py
│ └── routers
│ │ ├── __init__.py
│ │ ├── items.py
│ │ └── users.py
│ └── models
│ │ ├── __init__.py
│ │ ├── items.py
│ │ └── users.py
│ └── schemas
│ │ ├── __init__.py
│ │ ├── items.py
│ │ └── users.py
│ └── internal
│ │ ├── __init__.py
│ │ └── admin.py
here models represent db models and schemas represent pydantic models
Django project structure
Parent directory: personal_portfolio_project
Main sub-direcory (containing the settings): personal_portfolio
Apps: portfolio, blog
Directory structure of the entire project with all its apps (printed out in the console via tree):
├── personal_portfolio_project
│ ├── blog
│ │ ├── __init__.py
│ │ ├── __pycache__
│ │ │ ├── __init__.cpython-39.pyc
│ │ │ ├── admin.cpython-39.pyc
│ │ │ ├── models.cpython-39.pyc
│ │ │ ├── urls.cpython-39.pyc
│ │ │ └── views.cpython-39.pyc
│ │ ├── admin.py
│ │ ├── apps.py
│ │ ├── migrations
│ │ │ ├── 0001_initial.py
│ │ │ ├── 0002_auto_20210117_1306.py
│ │ │ ├── 0003_auto_20210117_1309.py
│ │ │ ├── 0004_auto_20210117_1432.py
│ │ │ ├── __init__.py
│ │ │ └── __pycache__
│ │ │ ├── 0001_initial.cpython-39.pyc
│ │ │ ├── 0002_auto_20210117_1306.cpython-39.pyc
│ │ │ ├── 0003_auto_20210117_1309.cpython-39.pyc
│ │ │ ├── 0004_auto_20210117_1432.cpython-39.pyc
│ │ │ └── __init__.cpython-39.pyc
│ │ ├── models.py
│ │ ├── templates
│ │ │ └── blog
│ │ │ ├── all_blogs.html
│ │ │ └── detail.html
│ │ ├── tests.py
│ │ ├── urls.py
│ │ └── views.py
│ ├── db.sqlite3
│ ├── manage.py
│ ├── media
│ │ └── portfolio
│ │ └── images
│ │ ├── DSC_0004a.jpg
│ │ └── DSC_0010a.jpg
│ ├── personal_portfolio
│ │ ├── __init__.py
│ │ ├── __pycache__
│ │ │ ├── __init__.cpython-39.pyc
│ │ │ ├── settings.cpython-39.pyc
│ │ │ ├── urls.cpython-39.pyc
│ │ │ └── wsgi.cpython-39.pyc
│ │ ├── asgi.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ ├── portfolio
│ │ ├── __init__.py
│ │ ├── __pycache__
│ │ │ ├── __init__.cpython-39.pyc
│ │ │ ├── admin.cpython-39.pyc
│ │ │ ├── forms.cpython-39.pyc
│ │ │ ├── models.cpython-39.pyc
│ │ │ └── views.cpython-39.pyc
│ │ ├── admin.py
│ │ ├── apps.py
│ │ ├── forms.py
│ │ ├── migrations
│ │ │ ├── 0001_initial.py
│ │ │ ├── __init__.py
│ │ │ └── __pycache__
│ │ │ ├── 0001_initial.cpython-39.pyc
│ │ │ └── __init__.cpython-39.pyc
│ │ ├── models.py
│ │ ├── static
│ │ │ └── portfolio
│ │ │ ├── CV_Andreas_Luckert.pdf
│ │ │ ├── DSC_0010a.jpg
│ │ │ ├── Logo_Andreas_Luckert.png
│ │ │ ├── Logo_ZappyCode_DjangoCourse.png
│ │ │ ├── custom.css
│ │ │ └── main.js
│ │ ├── templates
│ │ │ └── portfolio
│ │ │ ├── about.html
│ │ │ ├── base.html
│ │ │ └── home.html
│ │ ├── tests.py
│ │ └── views.py
│ └── test_sendgrid.py
Story - line
I have a portfolio/forms.py - file in the "portfolio" - app with the following content:
from django import forms
class ContactForm(forms.Form):
from_email = forms.EmailField(required=True)
subject = forms.CharField(required=True)
message = forms.CharField(widget=forms.Textarea, required=True)
# From docs: https://docs.djangoproject.com/en/3.1/topics/forms/#more-on-fields
cc_myself = forms.BooleanField(required=False)
In the portfolio/views.py I have the following relevant part:
from .forms import ContactForm # import module from same parent folder as this script
import General.Misc.general_tools as tools # custom module to import special-print function
def contactView(request, admin_email='blaa#blaaa.com'):
if request.method == 'GET':
sendemail_form = ContactForm()
else:
sendemail_form = ContactForm(request.POST)
if sendemail_form.is_valid():
# * Retrieve data and set up e-mail (subject, sender, message)
subject = sendemail_form.cleaned_data['subject']
from_email = sendemail_form.cleaned_data['from_email']
message = sendemail_form.cleaned_data['message']
# From docs: https://docs.djangoproject.com/en/3.1/topics/forms/#more-on-fields
cc_myself = sendemail_form.cleaned_data['cc_myself']
recipients = [admin_email]
if cc_myself:
recipients.append(from_email)
# * Send email
try:
# General Django docs: https://docs.djangoproject.com/en/3.1/topics/email/#send-mail
# NOTE on passed list as 4th parameter: recipient-list
## i) How to set up an email contact form - docs: https://learndjango.com/tutorials/django-email-contact-form
send_mail(subject,
message,
from_email,
recipients,
fail_silently=False)
# * Exceptions * #
# NOTE on scope: security
except BadHeaderError:
return HttpResponse('Invalid header found.')
# General exception to be printed out
except Exception as e:
tools.except_print(f"ERROR:\n{e}")
# Return success (if this code is reached)
return redirect('success')
# Send the completed (filled-in) form to the homepage - HTML - document
return render(request, "portfolio/home.html",
{'sendemail_form': sendemail_form})
def successView(request):
return HttpResponse('Success! Thank you for your message.')
This is the contact form I included at the very end of my portfolio/templates/portfolio/home.html (the rest of the HTML works perfectly fine and generally Django does not show any errors in the console while displaying the localhost-webpage):
<!-- ** E-Mail contact form **
Docs: https://learndjango.com/tutorials/django-email-contact-form-->
{% if sendemail_form %}
<h2 class="mt-5">Contact Me</h2>
<hr>
<form method="post">
{% csrf_token %}
<!-- NOTE on ".as_p": https://docs.djangoproject.com/en/3.1/topics/forms/#form-rendering-options
Possible attributes: as_table, as_p, as_ul -->
{{ sendemail_form.as_p }}
<div class="form-actions">
<button type="submit">Send</button>
</div>
</form>
{% endif %}
The content of my personal_portfolio/urls.py is the following:
from django.contrib import admin
from django.urls import path, include
from django.conf.urls.static import static
from django.conf import settings # get info from the settings.py - file
from portfolio import views # other views could also be imported (e.g. from the blog-app)
urlpatterns = [
path("", views.home, name="home"),
# Docs: https://learndjango.com/tutorials/django-email-contact-form
path("contact/", views.contactView, name='contact'),
path("success/", views.successView, name='success'),
# Add "About me" for additonal info about the creator of the webpage
path("about/", views.about, name="about"),
# Standard admin page
path('admin/', admin.site.urls),
# NOTE on include: forwarding all /blog/ - related requests to the blog-app
path('blog/', include('blog.urls')),
]
# * Add a static link to the MEDIA - files * #
# NOTE on processing: import certain variables defined in the settings.py of this project
# Docs: google django images (or media (folder)) etc.
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
I tried to register the form in the portfolio/admin.py just like classes defined in portfolio/models.py, but first of all I don't know if that's necessary and secondly it didn't even work due to the TypeError: 'DeclarativeFieldsMetaclass' object is not iterable, so I removed the following code from the portfolio/admin.py afterwards:
from django.contrib import admin
# !!! CAUTON: TypeError: 'DeclarativeFieldsMetaclass' object is not iterable
from .forms import ContactForm
# NOTE on registering form: doesn't work due to the following error:
# TypeError: 'DeclarativeFieldsMetaclass' object is not iterable
admin.site.register(ContactForm)
By and large, I'm confused as to how to make the form finally appear on my portfolio/templates/portfolio/home.html.
As the {% if sendemail_form %} scans for a non-None form to be passed, this indicates that the form is not passed correctly as it should.
Test to check whether the function contactView() in views.py calling the contact-form is reached with a printout in it:
$ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
January 19, 2021 - 14:16:37
Django version 3.1.5, using settings 'personal_portfolio.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
[19/Jan/2021 14:16:42] "GET / HTTP/1.1" 200 31617
[19/Jan/2021 14:16:42] "GET /static/portfolio/custom.css HTTP/1.1" 304 0
[19/Jan/2021 14:16:42] "GET /static/portfolio/main.js HTTP/1.1" 304 0
[19/Jan/2021 14:16:43] "GET /static/portfolio/Logo_ZappyCode_DjangoCourse.png HTTP/1.1" 200 442438
--> According to the console it is not reached, since the printout does not appear.
You need to create the ContactForm in the home view that renders the home page. We need to do this because the form appears in the home page template; if we don't add the form in this view, then the contact form part of the template never will work because it receives None.
def home(request):
... your code here ...
# send empty contact form to template
sendemail_form = ContactForm()
return render(
request,
"portfolio/home.html",
{'sendemail_form': sendemail_form})
Then the contactView should never render a template, just redirect. The job of the contactView is to get the POST data and process it, and the redirect to a differnt view; it should never render a template.
def contactView(request, admin_email='blaa#blaaa.com'):
if request.method == 'POST':
sendemail_form = ContactForm(request.POST)
if sendemail_form.is_valid():
subject = sendemail_form.cleaned_data['subject']
from_email = sendemail_form.cleaned_data['from_email']
message = sendemail_form.cleaned_data['message']
cc_myself = sendemail_form.cleaned_data['cc_myself']
recipients = [admin_email]
if cc_myself:
recipients.append(from_email)
try:
send_mail(subject,
message,
from_email,
recipients,
fail_silently=False)
except BadHeaderError:
return HttpResponse('Invalid header found.')
except Exception as e:
tools.except_print(f"ERROR:\n{e}")
return HttpResponse('errors.')
# Return success (if this code is reached)
return redirect('success')
# return to home page
return redirect('home')
Also, point your contact form (which is located in the home page template) to the correct URL (which is the contact URL, not the home URL); you could change:
{% if sendemail_form %}
...
<form method="post">
...
{% endif %}
to
{% if sendemail_form %}
...
<form method="post" action="{% url 'contact' %}">
...
{% endif %}
I think you need to add the correct URL to the contact HTML form.
You say that the HTML form is located in the home page, but the HTML form does not specify a different action (the target URL), so it will post (send a HTTP POST request) to the home page URL, which uses the view views.home, and that is not what you need.
By specifying a different HTML action attribute in the form, the page will send the data to a different URL (should be contact), which should then use your view views.contactView.
Try changing your HTML code for the contact form (in the home page template) from
{% if sendemail_form %}
...
<form method="post">
...
{% endif %}
to
{% if sendemail_form %}
...
<form method="post" action="{% url 'contact' %}">
...
{% endif %}
Also, add the ContactForm() in the view of your home page as well.
I have
ModuleNotFoundError: No module named 'project'
while trying to run test_req.py
My project structure is:
├── instance/
│ ├── flask.cfg
├── project/
│ ├── __init__.py
│ ├── base_processing.py
│ ├── models.py
| ├── views.py
│ ├── templates/
│ │ ├── base.html
│ │ ├── login.html
│ │ ├── note.html
│ │ ├── notes.html
│ └── static/
│ │
│ └── tests/
│ ├── test_req.html
├── run.py
My UnitTest file is:
# project/test_req.py
import unittest
import os
from project import app
from project.models import db, User, Note
from project.views import *
TEST_DB = 'test.db'
class RequestTests(unittest.TestCase):
#classmethod
def setUpClass(cls):
app.config['TESTING'] = True
app.config['WTF_CSRF_ENABLED'] = False
app.config['DEBUG'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + \
os.path.join(app.config['BASEDIR'], TEST_DB)
app.secret_key = 'staytrue'
cls.app = app.test_client()
def setUp(self):
db.create_all()
def tearDown(self):
db.drop_all()
def test_main_page(self):
response = self.app.get('/', follow_redirects=True)
self.assertEqual(response.status_code, 200)
def test_auth(self):
u = User(username='testname1', password='1234567', email='cyber#mail.com')
db.session.add(u)
db.session.commit()
response = self.app.post('/login', data=dict(username='testname1', password='1234567'), follow_redirects=True)
with self.app.session_transaction() as sess:
self.assertEqual(sess['username'], 'testname1')
if __name__ == "__main__":
unittest.main()
Also my test work just fine with nose2, when I run it from my root directory. Also this is the first time I'm organizing my project layout this way.
Module is not a folder, it should be a .py file. As you don't have project.py file, you should not specify from project import app.
Specifying from project import app means that there is project.py file and you want to import class app from this file.
if your test_req.py and app.py files are located in the same folder, then just use: import app in your test_req.py
Also replace:
from project.models import db, User, Note
from project.views import *
to
from models import db, User, Note
from views import *
Further reading:
Python
modules
Importing
Also, I would recommend to use PyCharm Community Edition, it is free, multilpatform and open source, and will help you to solve such tasks just via two mouse clicks.
Assume we have the following project structure in the root folder of our project:
/folder1/MyPythonFile1.py
/folder1/folder11/MyPythonFile2.py
/folder2/MyApp.py
/folder1/MyPythonFile1.py file looks like that:
class Class1:
def __init__(self):
pass
class Class2:
def __init__(self):
pass
And /folder1/folder11/MyPythonFile2.py file looks like that:
class Class3:
def __init__(self):
pass
File /folder2/MyApp.py uses classes from aforecited files and looks like that:
from folder1.MyPythonFile1 import Class1
from folder1.folder11.MyPythonFile2 import Class3
obj1 = Class1()
obj3 = Class3()
Apply this example to your particular case and update your imports accordingly.
I have read everything on SO and all the django docs looking for a solution to this. My models work absolutely fine but i cant import them to my 'getnews.py' file in the same directory as models.py, i can to the views.py and there is absolutely no circular imports!
models.py:
from django.db import models
import datetime
from django.utils import timezone
class newsurls(models.Model):
title = models.CharField(max_length=200)
def __unicode__(self):
return unicode(self.title)
pub_date = models.DateTimeField("date published")
class newsblock(models.Model):
news = models.ForeignKey(newsurls)
url = models.URLField(max_length=2000)
def __unicode__(self):
return unicode(self.url)
image = models.URLField(max_length=2000)
favi = models.URLField(max_length=2000)
bgcolor = models.CharField(max_length=20)
tcolor = models.CharField(max_length=20)
genre = models.CharField(max_length=200)
in views.py: (works)
from news.models import newsurls, newsblock
getnews.py: (dont work)
from news.models import newsurls, newsblock
traceback:
Traceback (most recent call last):
File "/home/skru/newsshuffle/news/getnews.py", line 3, in <module>
from news.models import newsurls, newsblock
ImportError: No module named news.models
views.py:
from django.shortcuts import render_to_response
from news.models import newsurls, newsblock
try:
import cPickle as pickle
except:
import pickle
import random
def news(request):
newsDatabase = open('/home/skru/newsshuffle/news/storyDb.p', 'r+')
openData = newsDatabase.read()
dstory = pickle.loads(openData)
count = dstory['count']['links']
story = []
outstorys = []
keys = dstory.keys()
for key in keys:
if key != 'count':
story.append(dstory[key]['genre'])
story.append(dstory[key]['title'])
story.append(dstory[key]['url'])
story.append(dstory[key]['image'])
story.append(dstory[key]['bgcolor'])
story.append(dstory[key]['via'])
story.append(dstory[key]['tcolor'])
outstorys.append(story)
story = []
random.shuffle(outstorys)
lists = newsurls.objects.order_by('-pub_date')[:100]
return render_to_response('news/news.html',
{
'story_list':outstorys,
'count':count,
'lists':lists,
})
filepath:
├── db.sqlite3
├── manage.py
├── news
│ ├── static
│ │ └── news
│ │ ├── news.css
│ │ └── ...
│ ├── templates
│ │ └── news
│ │ ├── allnews.html
│ │ └── ...
│ ├── __init__.py
│ ├── admin.py
│ ├── dd.py
│ ├── getPrevDate.py
│ ├── getnews.py
│ ├── models.py
│ ├── storyDb.p
│ ├── tests.py
│ ├── urls.py
│ ├── views.py
└── newsshuffle
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
i have tried every sort of different import 'newsshuffle.news.models' etc..,
added the system path to the system path manually as suggested in other feeds please help!!
manage.py:
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "newsshuffle.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
added this because most other posts on SO point to setting DJANGO_SETTINGS_MODULE having to be set manually bur as you can see it already is
Given that directory structure, your import should be working.
How about trying a relative import instead? In both views.py and getnews.py this should work:
from models import ...
have You tried that?
from .models import newsurls, newsblock
Please also read about PEP8 http://legacy.python.org/dev/peps/pep-0008/ cause Your code is not formatted correctly.
EDITED: I see that You trying to do that but without '.' sign. Please note that before models is '.' like that .models