django和sqlalchemy集成

django-sorcer的Python项目详细描述


Build StatusRead The DocsPyPI versionCoveralls StatusBlack

sqlalchemy是一个优秀的orm。django是一个很好的框架,直到您决定不使用django orm。这个图书馆 提供实用程序、帮助程序和配置,以减轻在django中使用sqlalchemy的痛苦。它旨在提供 与使用django orm构建django应用程序类似的开发经验,使用sqlalchemy除外。

安装

pip install django-sorcery

快速启动

让我们从创建站点开始:

$ django-admin startproject mysite

让我们创建一个应用程序:

$cd mysite
$ python manage.py startapp polls

这将创建一个带有标准django应用程序布局的投票应用程序:

$ tree
.
├── manage.py
├── polls
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
└── mysite
   ├── __init__.py
   ├── settings.py
   ├── urls.py
   └── wsgi.py

3 directories, 12 files

让我们把pollsapp和django_sorceryin INSTALLED_APPSin mysite/settings.py

INSTALLED_APPS=['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','django_sorcery','polls.apps.PollsConfig',]

现在我们要做一个改变,开始用sqlalchemy构建我们的应用程序。让我们定义我们的模型 polls/models.py

fromdjango_sorcery.dbimportdatabasesdb=databases.get("default")classQuestion(db.Model):pk=db.Column(db.Integer(),autoincrement=True,primary_key=True)question_text=db.Column(db.String(length=200))pub_date=db.Column(db.DateTime())classChoice(db.Model):pk=db.Column(db.Integer(),autoincrement=True,primary_key=True)choice_text=db.Column(db.String(length=200))votes=db.Column(db.Integer(),default=0)question=db.ManyToOne(Question,backref=db.backref("choices",cascade="all, delete-orphan"))

现在我们有了一些模型,让我们使用alembicintegration:

$ python manage.py sorcery revision -m "Add question and poll models" polls
  Generating ./polls/migrations/3983fc419e10_add_question_and_poll_models.py ... done

让我们看看生成的迁移文件./polls/migrations/3983fc419e10_add_question_and_poll_models.py

"""
Add question and poll models

Revision ID: 3983fc419e10
Revises:
Create Date: 2019-04-16 20:57:48.154179
"""fromalembicimportopimportsqlalchemyassa# revision identifiers, used by Alembic.revision='3983fc419e10'down_revision=Nonebranch_labels=Nonedepends_on=Nonedefupgrade():# ### commands auto generated by Alembic - please adjust! ###op.create_table('question',sa.Column('pk',sa.Integer(),autoincrement=True,nullable=False),sa.Column('question_text',sa.String(length=200),nullable=True),sa.Column('pub_date',sa.DateTime(),nullable=True),sa.PrimaryKeyConstraint('pk'))op.create_table('choice',sa.Column('pk',sa.Integer(),autoincrement=True,nullable=False),sa.Column('choice_text',sa.String(length=200),nullable=True),sa.Column('votes',sa.Integer(),nullable=True),sa.Column('question_pk',sa.Integer(),nullable=True),sa.ForeignKeyConstraint(['question_pk'],['question.pk'],),sa.PrimaryKeyConstraint('pk'))# ### end Alembic commands ###defdowngrade():# ### commands auto generated by Alembic - please adjust! ###op.drop_table('choice')op.drop_table('question')# ### end Alembic commands ###

让我们看看生成的sql:

$ python manage.py sorcery upgrade --sql polls

CREATE TABLE alembic_version_polls (
   version_num VARCHAR(32) NOT NULL,
   CONSTRAINT alembic_version_polls_pkc PRIMARY KEY (version_num)
);

-- Running upgrade  -> d7d86e07cc8e

CREATE TABLE question (
   pk INTEGER NOT NULL,
   question_text VARCHAR(200),
   pub_date DATETIME,
   PRIMARY KEY (pk)
);

CREATE TABLE choice (
   pk INTEGER NOT NULL,
   choice_text VARCHAR(200),
   votes INTEGER,
   question_pk INTEGER,
   PRIMARY KEY (pk),
   FOREIGN KEY(question_pk) REFERENCES question (pk)
);

INSERT INTO alembic_version_polls (version_num) VALUES ('d7d86e07cc8e');

让我们更新数据库:

$ python manage.py sorcery upgrade
Running migrations for polls on database default

现在,我们有足够的资源进入Django Shell:

$ python manage.py shell
>>> from polls.models import Choice, Question, db  # Import the model classes and the db
# we have no choices or questions in db yet
>>> Choice.query.all()[]
>>> Question.query.all()[]

# Lets create a new question
>>> from django.utils import timezone
>>> q= Question(question_text="What's new?", pub_date=timezone.now())>>> q
Question(pk=None, pub_date=datetime.datetime(2018, 5, 19, 0, 54, 20, 778186, tzinfo=<UTC>), question_text="What's new?")

# lets save our question, we need to add our question to the db
>>> db.add(q)# at this point the question is in pending state
>>> db.new
IdentitySet([Question(pk=None, pub_date=datetime.datetime(2018, 5, 19, 0, 54, 20, 778186, tzinfo=<UTC>), question_text="What's new?")])

# lets flush to the database
>>> db.flush()# at this point our question is in persistent state and will receive a primary key
>>> q.pk
1

# lets change the question text
>>> q.question_text ="What's up?">>> db.flush()# Question.objects and Question.query are both query properties that return a query object bound to db
>>> Question.objects
<django_sorcery.db.query.Query at 0x7feb1c7899e8>
>>> Question.query
<django_sorcery.db.query.Query at 0x7feb1c9377f0>

# and lets see all the questions
>>> Question.objects.all()[Question(pk=1, pub_date=datetime.datetime(2018, 5, 19, 0, 54, 20, 778186, tzinfo=<UTC>), question_text="What's up?")]

>>> exit()

让我们在polls/views.py中添加几个视图,从列表视图开始:

fromdjango.shortcutsimportrenderfromdjango.templateimportloaderfromdjango.httpimportHttpResponseRedirectfromdjango.urlsimportreversefromdjango_sorcery.shortcutsimportget_object_or_404from.modelsimportQuestion,Choice,dbdefindex(request):latest_question_list=Question.objects.order_by(Question.pub_date.desc())[:5]context={'latest_question_list':latest_question_list}returnrender(request,'polls/index.html',context)defdetail(request,question_id):question=get_object_or_404(Question,pk=question_id)returnrender(request,'polls/detail.html',{'question':question})defresults(request,question_id):question=get_object_or_404(Question,pk=question_id)returnrender(request,'polls/results.html',{'question':question})defvote(request,question_id):question=get_object_or_404(Question,pk=question_id)selected_choice=Choice.query.filter(Choice.question==question,Choice.pk==request.POST['choice'],).one_or_none()ifnotselected_choice:returnrender(request,'polls/detail.html',{'question':question,'error_message':"You didn't select a choice.",})selected_choice.votes+=1db.flush()returnHttpResponseRedirect(reverse('polls:results',args=(question.pk,)))

并在polls/urls.py

中注册视图
fromdjango.urlsimportpathfrom.importviewsapp_name='polls'urlpatterns=[path('',views.index,name='index'),path('<int:question_id>/',views.detail,name='detail'),path('<int:question_id>/results',views.results,name='results'),path('<int:question_id>/vote',views.vote,name='vote'),]

并注册SQLAlchemyMiddleware,为每个请求模式提供工作单元:

MIDDLEWARE=['django_sorcery.db.middleware.SQLAlchemyMiddleware',# ...]

并添加一些模板:

polls/templates/polls/index.html

{% if latest_question_list %}
<ul>
{% for question in latest_question_list %}
<li><ahref="{% url 'polls:detail' question.pk %}">{{ question.question_text }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}

polls/templates/polls/detail.html

<h1>{{ question.question_text }}</h1>

{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<formaction="{% url 'polls:vote' question.pk %}"method="post">
{% csrf_token %}
{% for choice in question.choices %}
   <inputtype="radio"name="choice"id="choice{{ forloop.counter }}"value="{{ choice.pk }}"/><labelfor="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br/>
{% endfor %}
<inputtype="submit"value="Vote"/></form>

polls/templates/polls/results.html

<h1>{{ question.question_text }}</h1><ul>
{% for choice in question.choices %}
   <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul><ahref="{% url 'polls:detail' question.pk %}">Vote again?</a>

这很好,但是我们可以使用泛型视图做得更好。让我们在polls/views.py

中调整视图
fromdjango.shortcutsimportrenderfromdjango.httpimportHttpResponseRedirectfromdjango.urlsimportreversefromdjango_sorcery.shortcutsimportget_object_or_404fromdjango_sorceryimportviewsfrom.modelsimportQuestion,Choice,dbclassIndexView(views.ListView):template_name='polls/index.html'context_object_name='latest_question_list'defget_queryset(self):returnQuestion.objects.order_by(Question.pub_date.desc())[:5]classDetailView(views.DetailView):model=Questionsession=dbtemplate_name='polls/detail.html'classResultsView(DetailView):template_name='polls/results.html'defvote(request,question_id):question=get_object_or_404(Question,pk=question_id)selected_choice=Choice.query.filter(Choice.question==question,Choice.pk==request.POST['choice'],).one_or_none()ifnotselected_choice:returnrender(request,'polls/detail.html',{'question':question,'error_message':"You didn't select a choice.",})selected_choice.votes+=1db.flush()returnHttpResponseRedirect(reverse('polls:results',args=(question.pk,)))

然后调整polls/urls.py如下:

fromdjango.urlsimportpathfrom.importviewsapp_name='polls'urlpatterns=[path('',views.IndexView.as_view(),name='index'),path('<int:pk>/',views.DetailView.as_view(),name='detail'),path('<int:pk>/results',views.ResultsView.as_view(),name='results'),path('<int:question_id>/vote',views.vote,name='vote'),]

template_namecontext_object_name的默认值与django的泛型视图类似。如果我们 handn没有定义那些模板名的默认值应该是polls/question_detail.htmlpolls/question_list.html用于详细信息和列表模板名称,而questionquestion_list用于上下文 详细视图和列表视图的名称。

这一切都很好,但我们甚至可以用一个视图集做得更好。让我们在polls/views.py

中调整视图
fromdjango.httpimportHttpResponseRedirectfromdjango.urlsimportreverse,reverse_lazyfromdjango_sorcery.routersimportactionfromdjango_sorcery.viewsetsimportModelViewSetfrom.modelsimportQuestion,Choice,dbclassPollsViewSet(ModelViewSet):model=Questionfields="__all__"destroy_success_url=reverse_lazy("polls:question-list")defget_success_url(self):returnreverse("polls:question-detail",kwargs={"pk":self.object.pk})@action(detail=True)defresults(self,request,*args,**kwargs):returnself.retrieve(request,*args,**kwargs)@action(detail=True,methods=["POST"])defvote(self,request,*args,**kwargs):self.object=self.get_object()selected_choice=Choice.query.filter(Choice.question==self.object,Choice.pk==request.POST.get("choice")).one_or_none()ifnotselected_choice:context=self.get_detail_context_data(object=self.object)context["error_message"]="You didn't select a choice."self.action="retrieve"returnself.render_to_response(context)selected_choice.votes+=1db.flush()returnHttpResponseRedirect(reverse("polls:question-results",args=(self.object.pk,)))

调整我们的polls/urls.py,就像:

fromdjango.urlsimportpath,includefromdjango_sorcery.routersimportSimpleRouterfrom.importviewsrouter=SimpleRouter()router.register("",views.PollsViewSet)app_name="polls"urlpatterns=[path("",include(router.urls))]

通过这些更改,我们将拥有以下URL:

$ ./manage.py run show_urls
/polls/      polls.views.PollsViewSet        polls:question-list
/polls/<pk>/ polls.views.PollsViewSet        polls:question-detail
/polls/<pk>/delete/  polls.views.PollsViewSet        polls:question-destroy
/polls/<pk>/edit/    polls.views.PollsViewSet        polls:question-edit
/polls/<pk>/results/ polls.views.PollsViewSet        polls:question-results
/polls/<pk>/vote/    polls.views.PollsViewSet        polls:question-vote
/polls/new/  polls.views.PollsViewSet        polls:question-new

这会将以下操作映射到视图集上的以下操作:

MethodPathActionRoute Name
GET/polls/listquestion-list
POST/polls/createquestion-list
GET/polls/new/newquestion-new
GET/polls/1/retrievequestion-detail
POST/polls/1/updatequestion-detail
PUT/polls/1/updatequestion-detail
PATCH/polls/1/updatequestion-detail
DELETE/polls/1/destroyquestion-detail
GET/polls/1/edit/editquestion-edit
GET/polls/1/delete/confirm_destoyquestion-delete
POST/polls/1/delete/destroyquestion-delete

现在,让我们添加一个内联表单集,以便能够向问题添加选项,调整polls/views.py

fromdjango.httpimportHttpResponseRedirectfromdjango.urlsimportreverse,reverse_lazyfromdjango_sorcery.routersimportactionfromdjango_sorcery.viewsetsimportModelViewSetfromdjango_sorcery.formsetsimportinlineformset_factoryfrom.modelsimportQuestion,Choice,dbChoiceFormSet=inlineformset_factory(relation=Question.choices,fields=(Choice.choice_text.key,),session=db)classPollsViewSet(ModelViewSet):model=Questionfields=(Question.question_text.key,Question.pub_date.key)destroy_success_url=reverse_lazy("polls:question-list")defget_success_url(self):returnreverse("polls:question-detail",kwargs={"pk":self.object.pk})defget_form_context_data(self,**kwargs):kwargs["choice_formset"]=self.get_choice_formset()returnsuper().get_form_context_data(**kwargs)defget_choice_formset(self,instance=None):ifnothasattr(self,"_choice_formset"):instance=instanceorself.objectself._choice_formset=ChoiceFormSet(instance=instance,data=self.request.POSTifself.request.POSTelseNone)returnself._choice_formsetdefprocess_form(self,form):ifform.is_valid()andself.get_choice_formset(instance=form.instance).is_valid():returnself.form_valid(form)returnform.invalid(self,form)defform_valid(self,form):self.object=form.save()self.object.choices=self.get_choice_formset().save()db.flush()returnHttpResponseRedirect(self.get_success_url())@action(detail=True)defresults(self,request,*args,**kwargs):returnself.retrieve(request,*args,**kwargs)@action(detail=True,methods=["POST"])defvote(self,request,*args,**kwargs):self.object=self.get_object()selected_choice=Choice.query.filter(Choice.question==self.object,Choice.pk==request.POST.get("choice")).one_or_none()ifnotselected_choice:context=self.get_detail_context_data(object=self.object)context["error_message"]="You didn't select a choice."self.action="retrieve"returnself.render_to_response(context)selected_choice.votes+=1db.flush()returnHttpResponseRedirect(reverse("polls:question-results",args=(self.object.pk,)))

polls/templates/question_edit.htmlpolls/templates/question_edit.html中添加choice_formset

<form...>
   ...
   {{ choice_formset }}
   ...
</form>

欢迎加入QQ群-->: 979659372 Python中文网_新手群

推荐PyPI第三方库


热门话题
java如何将jaxb插件扩展与gradlejaxbplugin一起使用   java Hibernate列表<Object[]>到特定对象   java使用多态性显示arraylist的输出   java水平堆叠卡,带有一定偏移量   java错误:找不到符号方法liesInt()   java客户机/服务器文件收发中的多线程流管理   在java中可以基于访问重载方法吗?   包含空元素的java排序数组   swing Java按钮/网格布局   java BottomNavigationView getmaxitemcount   java空指针异常字符串生成器   java Xamarin升级导致“类文件版本错误52.0,应为50.0”错误   java我正在尝试打印它,而不只是对每一行进行println   Tomcat7中的java是否需要复制上下文。将xml转换为conf/Catalina/locahost以使其生效   带有注入服务的java REST端点在何处引发自定义WebServiceException?   在Java中使用GPS数据   java如何将JFreeChart ChartPanel导出到包含添加的CrosshairOverlay的图像对象?   内置Eclipse期间的Java 8堆栈溢出   java在GWT编译的JavaScript中如何表示BigDecimal