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