From 1240a3c64ba1b6f9c978ff15b9da121521f3e64a Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Tue, 19 Apr 2016 07:39:17 +0200 Subject: [PATCH 1/6] Users are allowed to register now This includes sending a welcome email. --- Dockerfile | 1 + README.md | 12 ++++++++++-- app/main.py | 10 ++++++---- app/templates/layout.html | 9 ++++++--- app/templates/security/login_user.html | 1 - app/templates/security/register_user.html | 14 ++++++++++++++ 6 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 app/templates/security/register_user.html diff --git a/Dockerfile b/Dockerfile index 08db488..96c10bd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,5 +7,6 @@ RUN pip3 install -r /requirements.txt; \ ADD app /app VOLUME ["/data"] +VOLUME ["/app/config] EXPOSE 5000 ENTRYPOINT python3 /app/main.py diff --git a/README.md b/README.md index 5054800..c0aff72 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,14 @@ ## build `docker build --tag=$(basename $PWD) .` +## general configuration + +Look at *app/config/email.py.example** for the configuration of the +parameters required for sending emails. Copy the file as *email.py* to +a folder that will serve as configuration directory and fill in the +information. The directory will be used as volume during container +operation. + ## run in development Include the development version of the code as volume, so the app gets @@ -10,7 +18,7 @@ reloaded automatically. The sqlite file will be stored in *tmp* so it can be inspected with tools like *sqlite3*. The switch *DEBUG* enables debugging during development. -`docker run --name rollerverbrauch -ti -v `pwd`/app:/app -v /tmp/pitstops/:/data -e DEBUG=True -p 5000:5000 rollerverbrauch` +`docker run --rm --name rollerverbrauch -ti -v `pwd`/app:/app -v `pwd`/../rollerverbrauch_config:/app/config -v /tmp/pitstops/:/data -e DEBUG=True -p 5000:5000 rollerverbrauch` ## run in production -`docker run --name pitstops -d -v /data/pitstops/:/data -p 80:5000 rollerverbrauch` \ No newline at end of file +`docker run --name pitstops -d -v /data/pitstops/:/data -v /configs/pitstops/:/app/config -p 80:5000 rollerverbrauch` diff --git a/app/main.py b/app/main.py index 4a75aa5..4dfe233 100644 --- a/app/main.py +++ b/app/main.py @@ -1,5 +1,4 @@ from datetime import date -from datetime import datetime from flask import Flask from flask import render_template, make_response from flask import request, redirect, g @@ -7,9 +6,7 @@ from flask import url_for from flask_sqlalchemy import SQLAlchemy from flask.ext.security import Security, SQLAlchemyUserDatastore, \ UserMixin, RoleMixin, login_required, utils -import uuid -import hashlib -from functools import wraps +from flask.ext.mail import Mail, Message from flask_wtf import Form from wtforms import DateField, IntegerField, DecimalField from wtforms.validators import DataRequired, ValidationError @@ -23,8 +20,12 @@ db = SQLAlchemy(app) app.config['SECRET_KEY'] = 'development key' app.config['SECURITY_PASSWORD_HASH'] = 'pbkdf2_sha512' app.config['SECURITY_PASSWORD_SALT'] = 'xxxxxxxxxxxxxxxxxxxxxx' +app.config['SECURITY_REGISTERABLE'] = True +app.config['SECURITY_EMAIL_SENDER'] = 'pitstops@lusiardi.de' +app.config.from_object('config.email') app.config.from_object(__name__) +mail = Mail(app) roles_users = db.Table('roles_users', db.Column('user_id', db.Integer(), db.ForeignKey('user.id')), @@ -77,6 +78,7 @@ class Pitstop(db.Model): user_datastore = SQLAlchemyUserDatastore(db, User, Role) security = Security(app, user_datastore) + @app.before_first_request def before_first_request(): db.create_all() diff --git a/app/templates/layout.html b/app/templates/layout.html index f6ada95..d0ea304 100644 --- a/app/templates/layout.html +++ b/app/templates/layout.html @@ -1,9 +1,12 @@ {% macro navigation() -%} -
  • Create Pitstop
  • -
  • Statistics
  • -
  • Manual
  • {% if current_user.email %} +
  • Create Pitstop
  • +
  • Statistics
  • +
  • Manual
  • Logout
  • + {% else %} +
  • Login
  • +
  • Register
  • {% endif %} {%- endmacro %} diff --git a/app/templates/security/login_user.html b/app/templates/security/login_user.html index 536dd1c..ec744d1 100644 --- a/app/templates/security/login_user.html +++ b/app/templates/security/login_user.html @@ -10,5 +10,4 @@ {{ render_field(login_user_form.next) }} {{ render_field(login_user_form.submit) }} -{% include "security/_menu.html" %} {% endblock %} diff --git a/app/templates/security/register_user.html b/app/templates/security/register_user.html new file mode 100644 index 0000000..9ecd588 --- /dev/null +++ b/app/templates/security/register_user.html @@ -0,0 +1,14 @@ +{% extends "layout.html" %} +{% from "security/_macros.html" import render_field_with_errors, render_field %} + +{% block body %} +
    + {{ register_user_form.hidden_tag() }} + {{ render_field_with_errors(register_user_form.email) }} + {{ render_field_with_errors(register_user_form.password) }} + {% if register_user_form.password_confirm %} + {{ render_field_with_errors(register_user_form.password_confirm) }} + {% endif %} + {{ render_field(register_user_form.submit) }} +
    +{% endblock %} From 344db8d998b6bee75822bc708354e57abe2198af Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Tue, 19 Apr 2016 08:19:31 +0200 Subject: [PATCH 2/6] add example config file --- app/config/email.py.example | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 app/config/email.py.example diff --git a/app/config/email.py.example b/app/config/email.py.example new file mode 100644 index 0000000..0b968c8 --- /dev/null +++ b/app/config/email.py.example @@ -0,0 +1,6 @@ +MAIL_SERVER = 'smtp.gmail.com' +MAIL_PORT = 465 +MAIL_USE_TLS = False +MAIL_USE_SSL = True +MAIL_USERNAME = 'your-gmail-username' +MAIL_PASSWORD = 'your-gmail-password' \ No newline at end of file From 9eb7ec7c8aa91e87ae77be758192c77648cfe988 Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Tue, 19 Apr 2016 21:09:15 +0200 Subject: [PATCH 3/6] switched to environment variable for config file lookup --- .../{email.py.example => config.py.example} | 0 app/db/__pycache__/__init__.cpython-34.pyc | Bin 3322 -> 0 bytes app/main.py | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename app/config/{email.py.example => config.py.example} (100%) delete mode 100644 app/db/__pycache__/__init__.cpython-34.pyc diff --git a/app/config/email.py.example b/app/config/config.py.example similarity index 100% rename from app/config/email.py.example rename to app/config/config.py.example diff --git a/app/db/__pycache__/__init__.cpython-34.pyc b/app/db/__pycache__/__init__.cpython-34.pyc deleted file mode 100644 index fcd275fcfa11da5fea09bcf7bc1cdbf6817d272f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3322 zcmbVO-ESMm5ud#;QWRxb{&woL*Q82(u`Qu-kOF~X8?})ZXhEQ005=4J!|`?{opR(+ zd&k-)q$lUOeJhYZp?^)^@3l{P$sdrX{AP}%BsvCyp0vZc*}dKQ_|5G6(B0_#_ruNS z@2wI2kIp?F_$L_U9*8CyfGW}s<%&j%28tBFyOcXLaOh9kBkj|-Sml#(>9tD(NL=bi z!R$t-(|M9itHZ*679Qo(GE1z^I^Sbwid5a!NoA^RWO^w}7LNnw2}b!(5CaWx9H_&3 z3|v+P+v7k!XL0zzCmoP(i5yIu^qTN3(jC(4!b8|4y&?P>=}V+Hg~x)+q_>1$Cw+zV zRpGJt8tLo8Z;-wLn{L+5v*GM&C&ZKHNm=S5Et@x1n;-t=@f4%H0a6hiDLPT~8k8s& zL;8_3dsjZ>t)3R+va-{(D(q6$i`=?Z9_LwQ9@ee27>`X_)s7xUPVJQ@-ygK}Fy7B{ z^B=Npob%(=-DEP^)x+I5&c<04$2*f(b$ju0ne`DRz1jM+;m$&yDopUhzdS_oEih2_ zNOXUV351{@sb1vR08_P}=0#~DWm|ZY;~N4vQQeATZSpu~>zgChtRqK?_`-=xS8ZT% zb0XplSHVNQ*#|HbW``ez`?eT`ldLMMVp4{M)y9UyS7D~Zq)Y*(OYy|m{lbpo39t~zOi7bEoOuavn|Ids%e zkKjfi_ch}0(WeY!zxfI1(>~w<$?bmB>dkh4fzjHOX_#lDtO|c`Icl@^_E!(KM-R4j z?Qve!?LrqLQyE(az>qa%q*6z4?fzWa=>-NISt6>%A0rb_< zdv~zDWbBdJR9DnJHCwpw!m^az#t;Ac7~;YQV3rU=%;AC)hfZ8N5GSemoWp)=;0HiF z%EqO!RhW&d;tURV+2Z@*!s7ezj9480Bgvn+WsCElwz2=qh_0c&iUDt9n6nAA7b(!@Up}>>^%U|9XfRc zJ1|s0z}UIRC9e;6Fem^T0K@Cew&%bole}sYVhU%J;fq6KO*l?QCj2~nEO0=Hz#^22 zYZvn(o+LII*(*>4zL0o(yor4Wl2v;p;Jif?QUntKF40rbOB2Du05t!y1**jX3B(*p3ZSCb* zS=H_+nbhlC^lUb%vSM7f_f3@^qLkOZO~waC0$c~U0nJ!r2Z6RPcYzed1gSo?WnpWd z6Q!&Jl*DO{t!?q5?tsK|fuD`FSv5;Q!~iZibx2)TzgB^|ig8EXP+Mwt^DQ=}i$cI< zatVZgSS;2w*Eo6@|8VJ1qhI@WN@GaL^`#zK3G1+FqB}js8KLi)G zo5FpUr5`c5$Aoj$@&#jenCvp)05pViQtraf!OR1CWi_B%Z)m&>C08`wLvs3$U{K2c z6>$Vn($F#?L6OrOUqwf9f1w9}=Q-P+YofpXeLg2W!LqgTYtNcQH{fSm1{?Ehs)4<| z1=;+js@p(oF}10w)&8K1J~8H-3`MY9F|=+;GpL7u#@F-uwWhj!X>m_BpX^1KxK-rT zh~v5w$D=||bLP7-I;td}&v0v3q;YIJ*v)pCTw=nlp5lo4~ciq7023;3_LD$0wy6fF_%WKfP@ZBkGi)HuMK`}f+XDG8jw{_j#YfN~= O4M)kD7dV$y*Zm1VJ+jpR diff --git a/app/main.py b/app/main.py index 4dfe233..dd18609 100644 --- a/app/main.py +++ b/app/main.py @@ -22,7 +22,7 @@ app.config['SECURITY_PASSWORD_HASH'] = 'pbkdf2_sha512' app.config['SECURITY_PASSWORD_SALT'] = 'xxxxxxxxxxxxxxxxxxxxxx' app.config['SECURITY_REGISTERABLE'] = True app.config['SECURITY_EMAIL_SENDER'] = 'pitstops@lusiardi.de' -app.config.from_object('config.email') +app.config.from_envvar('config') app.config.from_object(__name__) mail = Mail(app) From 01798e95480e4fd348442cd51057c256d48aa9e8 Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Tue, 19 Apr 2016 22:41:40 +0200 Subject: [PATCH 4/6] adds admin page admin users must have role admin (which must currently be set manually via database) --- app/main.py | 43 +++++++++++++++++++-------------------- app/templates/admin.html | 5 +++++ app/templates/layout.html | 4 +++- 3 files changed, 29 insertions(+), 23 deletions(-) create mode 100644 app/templates/admin.html diff --git a/app/main.py b/app/main.py index dd18609..2b910e1 100644 --- a/app/main.py +++ b/app/main.py @@ -5,7 +5,8 @@ from flask import request, redirect, g from flask import url_for from flask_sqlalchemy import SQLAlchemy from flask.ext.security import Security, SQLAlchemyUserDatastore, \ - UserMixin, RoleMixin, login_required, utils + UserMixin, RoleMixin, login_required, roles_required, utils +from flask.ext.security import user_registered from flask.ext.mail import Mail, Message from flask_wtf import Form from wtforms import DateField, IntegerField, DecimalField @@ -17,11 +18,8 @@ DATABASE = '/data/rollerverbrauch.db' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+DATABASE db = SQLAlchemy(app) -app.config['SECRET_KEY'] = 'development key' app.config['SECURITY_PASSWORD_HASH'] = 'pbkdf2_sha512' -app.config['SECURITY_PASSWORD_SALT'] = 'xxxxxxxxxxxxxxxxxxxxxx' app.config['SECURITY_REGISTERABLE'] = True -app.config['SECURITY_EMAIL_SENDER'] = 'pitstops@lusiardi.de' app.config.from_envvar('config') app.config.from_object(__name__) @@ -57,7 +55,7 @@ class User(db.Model, UserMixin): ) def __repr__(self): - return '' % self.username + return '' % self.email class Pitstop(db.Model): @@ -79,26 +77,21 @@ user_datastore = SQLAlchemyUserDatastore(db, User, Role) security = Security(app, user_datastore) + +@user_registered.connect_via(app) +def user_registered_sighandler(app, user, confirm_token): + """ + Called after a user was created + """ + role = user_datastore.find_role('user') + user_datastore.add_role_to_user(user, role) + + @app.before_first_request def before_first_request(): db.create_all() - - user_datastore.find_or_create_role(name='admin', description='Administrator') - user_datastore.find_or_create_role(name='end-user', description='End user') - - encrypted_password = utils.encrypt_password('password') - if not user_datastore.get_user('someone@example.com'): - user_datastore.create_user(email='someone@example.com', password=encrypted_password) - if not user_datastore.get_user('admin@example.com'): - user_datastore.create_user(email='admin@example.com', password=encrypted_password) - - # Commit any database changes; the User and Roles must exist before we can add a Role to the User - db.session.commit() - - # Give one User has the "end-user" role, while the other has the "admin" role. (This will have no effect if the - # Users already have these Roles.) Again, commit any database changes. - user_datastore.add_role_to_user('someone@example.com', 'end-user') - user_datastore.add_role_to_user('admin@example.com', 'admin') + user_datastore.find_or_create_role(name='admin', description='Role for administrators') + user_datastore.find_or_create_role(name='user', description='Role for all users.') db.session.commit() @@ -175,6 +168,12 @@ def get_manual(): return render_template('manual.html', data=g.data) +@app.route('/admin', methods=['GET']) +@roles_required('admin') +def get_admin_page(): + return render_template('admin.html', data=g.data) + + @app.route('/statistics', methods=['GET']) @login_required def get_statistics(): diff --git a/app/templates/admin.html b/app/templates/admin.html new file mode 100644 index 0000000..8a93582 --- /dev/null +++ b/app/templates/admin.html @@ -0,0 +1,5 @@ +{% extends "layout.html" %} + +{% block body %} + Admin +{% endblock %} diff --git a/app/templates/layout.html b/app/templates/layout.html index d0ea304..83a8141 100644 --- a/app/templates/layout.html +++ b/app/templates/layout.html @@ -2,7 +2,9 @@ {% if current_user.email %}
  • Create Pitstop
  • Statistics
  • -
  • Manual
  • + {% if current_user.has_role('admin') %} +
  • Admin
  • + {% endif %}
  • Logout
  • {% else %}
  • Login
  • From 0303be794510e8c728a78a4811bf47a2c51c25cc Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Thu, 21 Apr 2016 08:11:57 +0200 Subject: [PATCH 5/6] more user handling features of flask-security activated Now users can recover their passwords and change them while logged in. --- app/main.py | 12 +++++++++++- app/templates/account.html | 7 +++++++ app/templates/admin.html | 9 ++++++++- app/templates/layout.html | 1 + app/templates/security/change_password.html | 13 +++++++++++++ app/templates/security/forgot_password.html | 11 +++++++++++ app/templates/security/login_user.html | 16 ++++++++++------ app/templates/security/register_user.html | 1 + app/templates/security/reset_password.html | 12 ++++++++++++ 9 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 app/templates/account.html create mode 100644 app/templates/security/change_password.html create mode 100644 app/templates/security/forgot_password.html create mode 100644 app/templates/security/reset_password.html diff --git a/app/main.py b/app/main.py index 2b910e1..147e7ab 100644 --- a/app/main.py +++ b/app/main.py @@ -8,6 +8,7 @@ from flask.ext.security import Security, SQLAlchemyUserDatastore, \ UserMixin, RoleMixin, login_required, roles_required, utils from flask.ext.security import user_registered from flask.ext.mail import Mail, Message +from flask_security.core import current_user from flask_wtf import Form from wtforms import DateField, IntegerField, DecimalField from wtforms.validators import DataRequired, ValidationError @@ -20,6 +21,8 @@ db = SQLAlchemy(app) app.config['SECURITY_PASSWORD_HASH'] = 'pbkdf2_sha512' app.config['SECURITY_REGISTERABLE'] = True +app.config['SECURITY_CHANGEABLE'] = True +app.config['SECURITY_RECOVERABLE'] = True app.config.from_envvar('config') app.config.from_object(__name__) @@ -77,7 +80,6 @@ user_datastore = SQLAlchemyUserDatastore(db, User, Role) security = Security(app, user_datastore) - @user_registered.connect_via(app) def user_registered_sighandler(app, user, confirm_token): """ @@ -171,9 +173,17 @@ def get_manual(): @app.route('/admin', methods=['GET']) @roles_required('admin') def get_admin_page(): + g.data['users'] = User.query.all() return render_template('admin.html', data=g.data) +@app.route('/account', methods=['GET']) +@login_required +def get_account_page(): + print(current_user) + return render_template('account.html', data=g.data) + + @app.route('/statistics', methods=['GET']) @login_required def get_statistics(): diff --git a/app/templates/account.html b/app/templates/account.html new file mode 100644 index 0000000..de46b10 --- /dev/null +++ b/app/templates/account.html @@ -0,0 +1,7 @@ +{% extends "layout.html" %} + +{% block body %} +

    Account management for {{current_user.email}}

    + + Change password +{% endblock %} diff --git a/app/templates/admin.html b/app/templates/admin.html index 8a93582..97c2db5 100644 --- a/app/templates/admin.html +++ b/app/templates/admin.html @@ -1,5 +1,12 @@ {% extends "layout.html" %} {% block body %} - Admin +

    Admin

    + We have {{ data.users|length }} users so far: +
      + {% for user in data.users %} +
    • {{user.email}}
    • + {% endfor %} +
    + Login {% endblock %} diff --git a/app/templates/layout.html b/app/templates/layout.html index 83a8141..29fb991 100644 --- a/app/templates/layout.html +++ b/app/templates/layout.html @@ -2,6 +2,7 @@ {% if current_user.email %}
  • Create Pitstop
  • Statistics
  • +
  • Account
  • {% if current_user.has_role('admin') %}
  • Admin
  • {% endif %} diff --git a/app/templates/security/change_password.html b/app/templates/security/change_password.html new file mode 100644 index 0000000..27f5d62 --- /dev/null +++ b/app/templates/security/change_password.html @@ -0,0 +1,13 @@ +{% extends "layout.html" %} +{% from "security/_macros.html" import render_field_with_errors, render_field %} + +{% block body %} +

    Change password

    +
    + {{ change_password_form.hidden_tag() }} + {{ render_field_with_errors(change_password_form.password) }} + {{ render_field_with_errors(change_password_form.new_password) }} + {{ render_field_with_errors(change_password_form.new_password_confirm) }} + {{ render_field(change_password_form.submit) }} +
    +{% endblock %} diff --git a/app/templates/security/forgot_password.html b/app/templates/security/forgot_password.html new file mode 100644 index 0000000..556f254 --- /dev/null +++ b/app/templates/security/forgot_password.html @@ -0,0 +1,11 @@ +{% extends "layout.html" %} +{% from "security/_macros.html" import render_field_with_errors, render_field %} + +{% block body %} +

    Send password reset instructions

    +
    + {{ forgot_password_form.hidden_tag() }} + {{ render_field_with_errors(forgot_password_form.email) }} + {{ render_field(forgot_password_form.submit) }} +
    +{% endblock %} diff --git a/app/templates/security/login_user.html b/app/templates/security/login_user.html index ec744d1..9bc86c2 100644 --- a/app/templates/security/login_user.html +++ b/app/templates/security/login_user.html @@ -2,12 +2,16 @@ {% from "security/_macros.html" import render_field_with_errors, render_field %} {% block body %} +

    Login

    - {{ login_user_form.hidden_tag() }} - {{ render_field_with_errors(login_user_form.email) }} - {{ render_field_with_errors(login_user_form.password) }} - {{ render_field_with_errors(login_user_form.remember) }} - {{ render_field(login_user_form.next) }} - {{ render_field(login_user_form.submit) }} + {{ login_user_form.hidden_tag() }} + {{ render_field_with_errors(login_user_form.email) }} + {{ render_field_with_errors(login_user_form.password) }} + {{ render_field_with_errors(login_user_form.remember) }} + {{ render_field(login_user_form.next) }} + {{ render_field(login_user_form.submit) }} + {% if security.recoverable %} + Forgot password + {% endif %}
    {% endblock %} diff --git a/app/templates/security/register_user.html b/app/templates/security/register_user.html index 9ecd588..9cad77d 100644 --- a/app/templates/security/register_user.html +++ b/app/templates/security/register_user.html @@ -2,6 +2,7 @@ {% from "security/_macros.html" import render_field_with_errors, render_field %} {% block body %} +

    Register User

    {{ register_user_form.hidden_tag() }} {{ render_field_with_errors(register_user_form.email) }} diff --git a/app/templates/security/reset_password.html b/app/templates/security/reset_password.html new file mode 100644 index 0000000..f94ec4d --- /dev/null +++ b/app/templates/security/reset_password.html @@ -0,0 +1,12 @@ +{% extends "layout.html" %} +{% from "security/_macros.html" import render_field_with_errors, render_field %} + +{% block body %} +

    Reset password

    + + {{ reset_password_form.hidden_tag() }} + {{ render_field_with_errors(reset_password_form.password) }} + {{ render_field_with_errors(reset_password_form.password_confirm) }} + {{ render_field(reset_password_form.submit) }} +
    +{% endblock %} From 4261a9bb0b2c8b4da948290cf652a6f58a824213 Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Thu, 21 Apr 2016 08:12:12 +0200 Subject: [PATCH 6/6] reworked git ignore file --- .gitignore | 184 +---------------------------------------------------- 1 file changed, 3 insertions(+), 181 deletions(-) diff --git a/.gitignore b/.gitignore index 179dd1d..28ebf13 100644 --- a/.gitignore +++ b/.gitignore @@ -1,181 +1,3 @@ -### Python template -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -*.egg-info/ -.installed.cfg -*.egg - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*,cover -.hypothesis/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py - -# Flask instance folder -instance/ - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# IPython Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# dotenv -.env - -# virtualenv -venv/ -ENV/ - -# Spyder project settings -.spyderproject - -# Rope project settings -.ropeproject - -### Python template -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -*.egg-info/ -.installed.cfg -*.egg - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*,cover -.hypothesis/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py - -# Flask instance folder -instance/ - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# IPython Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# dotenv -.env - -# virtualenv -venv/ -ENV/ - -# Spyder project settings -.spyderproject - -# Rope project settings -.ropeproject - -# Created by .ignore support plugin (hsz.mobi) +.idea/ +**.DS_Store +**.swp