rm app.db ./db_create.py
del app.db flask/Scripts/python db_create.py
sqlalchemy.exc.IntegrityError IntegrityError: (IntegrityError) UNIQUE constraint failed: user.nickname u'UPDATE user SET nickname=?, about_me=? WHERE user.id = ?' (u'dup', u'', 2)
User
model, the nickname
field is declared as unique=True
, which is why this situation arose.debug=True
argument passed to the run
method. When we run the application on the battle server, you will need to make sure that the debug mode is turned off. For this we need another script (file runp.py
) : #!flask/bin/python from app import app app.run(debug = False)
./runp.py
500 Internal Server Error
. Flask generates this page when the debug mode is turned off and an exception occurs. The page looks so-so, but we at least do not disclose unnecessary details about the application.errorhandler
decorator ( app/views.py
) : @app.errorhandler(404) def not_found_error(error): return render_template('404.html'), 404 @app.errorhandler(500) def internal_error(error): db.session.rollback() return render_template('500.html'), 500
db.session.rollback()
. This function will be called as a result of the exception. If the exception was caused by an error interacting with the database, we need to roll back the current session. <!-- extend base layout --> {% extends "base.html" %} {% block content %} <h1>File Not Found</h1> <p><a href="{{url_for('index')}}">Back</a></p> {% endblock %}
<!-- extend base layout --> {% extends "base.html" %} {% block content %} <h1>An unexpected error has occurred</h1> <p>The administrator has been notified. Sorry for the inconvenience!</p> <p><a href="{{url_for('index')}}">Back</a></p> {% endblock %}
base.html
as the parent template, as a result of which both HTTP error messages look the same as the rest of our microblog pages.config.py
) : # mail server settings MAIL_SERVER = 'localhost' MAIL_PORT = 25 MAIL_USERNAME = None MAIL_PASSWORD = None # administrator list ADMINS = ['you@example.com']
logging
module from the standard Python library, so setting up sending error messages to mail will be quite simple (file app/__init__.py
) : from config import basedir, ADMINS, MAIL_SERVER, MAIL_PORT, MAIL_USERNAME, MAIL_PASSWORD if not app.debug: import logging from logging.handlers import SMTPHandler credentials = None if MAIL_USERNAME or MAIL_PASSWORD: credentials = (MAIL_USERNAME, MAIL_PASSWORD) mail_handler = SMTPHandler((MAIL_SERVER, MAIL_PORT), 'no-reply@' + MAIL_SERVER, ADMINS, 'microblog failure', credentials) mail_handler.setLevel(logging.ERROR) app.logger.addHandler(mail_handler)
python -m smtpd -n -c DebuggingServer localhost:25
app/__init__.py
) : if not app.debug: import logging from logging.handlers import RotatingFileHandler file_handler = RotatingFileHandler('tmp/microblog.log', 'a', 1 * 1024 * 1024, 10) file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]')) app.logger.setLevel(logging.INFO) file_handler.setLevel(logging.INFO) app.logger.addHandler(file_handler) app.logger.info('microblog startup')
tmp
folder under the name microblog.log
. We used RotatingFileHandler
, which allows us to set a limit on the amount of stored data. In our case, the file size is limited to one megabyte, while the last ten files are saved.logging.Formatter
provides the ability to set an arbitrary format of records in the log. Since we want to receive as much detailed information as possible, we will save the message itself, the timestamp , the status of the record, as well as the file name and line number from which the recording was initiated.app.logger
and file_handler
, this will allow us to record not only errors, but also other information that may be useful. For example, we will record the launch time of the application. Now every time microblogging is launched without debug mode, this event will be saved to the log.after_login
handler, which is called when the user is authorized in the system and we need to create a new User object. Here is what we can do to get rid of the problem: (file app/views.py
) : if user is None: nickname = resp.nickname if nickname is None or nickname == "": nickname = resp.email.split('@')[0] nickname = User.make_unique_nickname(nickname) user = User(nickname = nickname, email = resp.email, role = ROLE_USER) db.session.add(user) db.session.commit()
app/models.py
) : class User(db.Model): # ... @staticmethod def make_unique_nickname(nickname): if User.query.filter_by(nickname = nickname).first() == None: return nickname version = 2 while True: new_nickname = nickname + str(version) if User.query.filter_by(nickname = new_nickname).first() == None: break version += 1 return new_nickname # ...
app/forms.py
validate
method (file app/forms.py
) : from app.models import User class EditForm(Form): nickname = TextField('nickname', validators = [Required()]) about_me = TextAreaField('about_me', validators = [Length(min = 0, max = 140)]) def __init__(self, original_nickname, *args, **kwargs): Form.__init__(self, *args, **kwargs) self.original_nickname = original_nickname def validate(self): if not Form.validate(self): return False if self.nickname.data == self.original_nickname: return True user = User.query.filter_by(nickname = self.nickname.data).first() if user != None: self.nickname.errors.append('This nickname is already in use. Please choose another one.') return False return True
original_nickname
. The validate
method uses it to determine if the nickname has changed or not. If it has changed, we check for uniqueness. @app.route('/edit', methods = ['GET', 'POST']) @login_required def edit(): form = EditForm(g.user.nickname) # ...
app/templates/edit.html
) : <td>Your nickname:</td> <td> {{form.nickname(size = 24)}} {% for error in form.errors.nickname %} <br><span style="color: red;">[{{error}}]</span> {% endfor %} </td>
unittest
module ( app/tests.py
) : #!flask/bin/python import os import unittest from config import basedir from app import app, db from app.models import User class TestCase(unittest.TestCase): def setUp(self): app.config['TESTING'] = True app.config['CSRF_ENABLED'] = False app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'test.db') self.app = app.test_client() db.create_all() def tearDown(self): db.session.remove() db.drop_all() def test_avatar(self): u = User(nickname = 'john', email = 'john@example.com') avatar = u.avatar(128) expected = 'http://www.gravatar.com/avatar/d4c74594d841139328695756648b6bd6' assert avatar[0:len(expected)] == expected def test_make_unique_nickname(self): u = User(nickname = 'john', email = 'john@example.com') db.session.add(u) db.session.commit() nickname = User.make_unique_nickname('john') assert nickname != 'john' u = User(nickname = nickname, email = 'susan@example.com') db.session.add(u) db.session.commit() nickname2 = User.make_unique_nickname('john') assert nickname2 != 'john' assert nickname2 != nickname if __name__ == '__main__': unittest.main()
unittest
module unittest
is beyond the scope of this article. If you are not familiar with this module, for now you can simply assume that our tests are in the TestCase
class. The setUp
and tearDown
have a special meaning; they are performed before and after each test, respectively. In more complex cases, several test groups can be defined, where each group is a subclass of unittest.TestCase
, then each group will have its own setUp
and tearDown
.setUp
, the application configuration changes slightly. For example, we use a separate database for testing. In the tearDown
method tearDown
we clear the database.make_unique_nickname
method we just wrote for the User class. First a user is created with the name 'john'. After it is stored in the database, the method under test should return a nickname other than 'john'. Then we create and save the second user with the proposed nickname and check that another call to make_unique_nickname
will return a name that is different from the names of both created users. ./tests.py
Source: https://habr.com/ru/post/223783/
All Articles