Compare commits
	
		
			11 Commits
		
	
	
		
			v1.2.1
			...
			developmen
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 25fb3fdda4 | |||
| fa104d7d1b | |||
| cb747b4832 | |||
| 845fdcdf8d | |||
| 54e1653cb5 | |||
| 716206dc65 | |||
| a8eda4078d | |||
| d28cd6daed | |||
| 57b25cd214 | |||
| 8013a776a9 | |||
| aa1f46ee62 | 
@@ -1,4 +1,4 @@
 | 
				
			|||||||
FROM python:3.10-slim
 | 
					FROM python:3.13-slim
 | 
				
			||||||
ARG DATA=./data/
 | 
					ARG DATA=./data/
 | 
				
			||||||
ENV DATA=$DATA
 | 
					ENV DATA=$DATA
 | 
				
			||||||
WORKDIR /ref-test
 | 
					WORKDIR /ref-test
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -61,9 +61,7 @@ def create_app():
 | 
				
			|||||||
    app.register_blueprint(view, url_prefix='/admin/view')
 | 
					    app.register_blueprint(view, url_prefix='/admin/view')
 | 
				
			||||||
    app.register_blueprint(analysis, url_prefix='/admin/analysis')
 | 
					    app.register_blueprint(analysis, url_prefix='/admin/analysis')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    """Create Database Tables before First Request"""
 | 
					    """Create Database Tables when creating app"""
 | 
				
			||||||
    @app.before_first_request
 | 
					 | 
				
			||||||
    def _create_database_tables():
 | 
					 | 
				
			||||||
    with app.app_context():
 | 
					    with app.app_context():
 | 
				
			||||||
        db.create_all()
 | 
					        db.create_all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -54,7 +54,7 @@
 | 
				
			|||||||
                        </td>
 | 
					                        </td>
 | 
				
			||||||
                        <td>
 | 
					                        <td>
 | 
				
			||||||
                            {% if entry.end_time %}
 | 
					                            {% if entry.end_time %}
 | 
				
			||||||
                                {{ entry.end_time.strftime('%d %b %Y') }}
 | 
					                                {{ entry.end_time.strftime('%Y-%m-%d %H:%M') }}
 | 
				
			||||||
                            {% endif %}
 | 
					                            {% endif %}
 | 
				
			||||||
                        </td>
 | 
					                        </td>
 | 
				
			||||||
                        <td>
 | 
					                        <td>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,7 +43,7 @@
 | 
				
			|||||||
                            {{ element.get_name() }}
 | 
					                            {{ element.get_name() }}
 | 
				
			||||||
                        </td>
 | 
					                        </td>
 | 
				
			||||||
                        <td>
 | 
					                        <td>
 | 
				
			||||||
                            {{ element.date.strftime('%d %b %Y %H:%M') }}
 | 
					                            {{ element.date.strftime('%Y-%m-%d %H:%M') }}
 | 
				
			||||||
                        </td>
 | 
					                        </td>
 | 
				
			||||||
                        <td>
 | 
					                        <td>
 | 
				
			||||||
                            {{ element.creator.get_username() }}
 | 
					                            {{ element.creator.get_username() }}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,13 +33,13 @@
 | 
				
			|||||||
                {% for test in tests %}
 | 
					                {% for test in tests %}
 | 
				
			||||||
                    <tr class="table-row">
 | 
					                    <tr class="table-row">
 | 
				
			||||||
                        <td>
 | 
					                        <td>
 | 
				
			||||||
                            {{ test.start_date.strftime('%d %b %y %H:%M') }}
 | 
					                            {{ test.start_date.strftime('%Y-%m-%d %H:%M') }}
 | 
				
			||||||
                        </td>
 | 
					                        </td>
 | 
				
			||||||
                        <td>
 | 
					                        <td>
 | 
				
			||||||
                            {{ test.get_code() }}
 | 
					                            {{ test.get_code() }}
 | 
				
			||||||
                        </td>
 | 
					                        </td>
 | 
				
			||||||
                        <td>
 | 
					                        <td>
 | 
				
			||||||
                            {{ test.end_date.strftime('%d %b %Y %H:%M') }}
 | 
					                            {{ test.end_date.strftime('%Y-%m-%d %H:%M') }}
 | 
				
			||||||
                        </td>
 | 
					                        </td>
 | 
				
			||||||
                        <td>
 | 
					                        <td>
 | 
				
			||||||
                            {% if test.time_limit == None -%}
 | 
					                            {% if test.time_limit == None -%}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,69 +8,44 @@
 | 
				
			|||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block content %}
 | 
					{% block content %}
 | 
				
			||||||
    <h1>Analysis by {{ type[0]|upper }}{{ type[1:] }}</h1>
 | 
					    <h1>Analysis</h1>
 | 
				
			||||||
    <div class="container">
 | 
					    <div class="container">
 | 
				
			||||||
        <p class="lead">
 | 
					        <p class="lead">
 | 
				
			||||||
            The analysis section displays statistics for all test results as well as answers to individual questions.
 | 
					            Analysis for {{ type }} {{ subject }}.
 | 
				
			||||||
            Analysis reports can be generated per exam or per question dataset to identify common mistakes or patterns in answers.
 | 
					 | 
				
			||||||
        </p>
 | 
					        </p>
 | 
				
			||||||
        <div class="input-group mb-3">
 | 
					 | 
				
			||||||
            <span class="input-group-text">
 | 
					 | 
				
			||||||
                {% if type == 'exam' %}
 | 
					 | 
				
			||||||
                    Exam Code
 | 
					 | 
				
			||||||
                {% elif type == 'dataset' %}
 | 
					 | 
				
			||||||
                    Dataset Name
 | 
					 | 
				
			||||||
                {% endif %}
 | 
					 | 
				
			||||||
            </span>
 | 
					 | 
				
			||||||
            <span class="form-control">
 | 
					 | 
				
			||||||
                {{ subject }}
 | 
					 | 
				
			||||||
            </span>
 | 
					 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
        <div class="input-group mb-3">
 | 
					    <div class="container">
 | 
				
			||||||
            <span class="input-group-text">Total Entries</span>
 | 
					        <h3>
 | 
				
			||||||
            <span class="form-control">
 | 
					            Question List
 | 
				
			||||||
                {{ analysis.entries }}
 | 
					        </h3>
 | 
				
			||||||
            </span>
 | 
					        <div class="container dataset-metadata">
 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <div class="input-group mb-3">
 | 
					 | 
				
			||||||
            <span class="input-group-text">Passed</span>
 | 
					 | 
				
			||||||
            <span class="form-control">
 | 
					 | 
				
			||||||
                {{ analysis.grades.merit + analysis.grades.pass }} ({{ ((analysis.grades.merit + analysis.grades.pass)*100/analysis.entries)|round(2) }} %)
 | 
					 | 
				
			||||||
            </span>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <div class="mb-3">
 | 
					 | 
				
			||||||
            <span class="badge rounded-pill progress-bar-striped bg-success">Merit: {{ analysis.grades.merit }}</span> <span class="badge rounded-pill bg-primary progress-bar-striped">Pass: {{ analysis.grades.pass }}</span> <span class="badge rounded-pill progress-bar-striped bg-danger">Fail: {{ analysis.grades.fail }}</span>
 | 
					 | 
				
			||||||
            <div class="my-1 progress">
 | 
					 | 
				
			||||||
                <div class="progress-bar progress-bar-striped bg-success" role="progressbar" style="width: {{ (analysis.grades.merit*100/analysis.entries)|round(2) }}%" aria-valuenow="{{ analysis.grades.merit }}" aria-valuemin="0" aria-valuemax="{{ analysis.entries }}">{{ (analysis.grades.merit*100/analysis.entries)|round(2) }} %</div>
 | 
					 | 
				
			||||||
                <div class="progress-bar progress-bar-striped" role="progressbar" style="width: {{ (analysis.grades.pass*100/analysis.entries)|round(2) }}%" aria-valuenow="{{ analysis.grades.pass }}" aria-valuemin="0" aria-valuemax="{{ analysis.entries }}">{{ (analysis.grades.pass*100/analysis.entries)|round(2) }} %</div>
 | 
					 | 
				
			||||||
                <div class="progress-bar progress-bar-striped bg-danger" role="progressbar" style="width: {{ (analysis.grades.fail*100/analysis.entries)|round(2) }}%" aria-valuenow="{{ analysis.grades.fail }}" aria-valuemin="0" aria-valuemax="{{ analysis.entries }}">{{ (analysis.grades.fail*100/analysis.entries)|round(2) }} %</div>
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <div class="input-group mb-3">
 | 
					 | 
				
			||||||
            <span class="input-group-text">Mean Score</span>
 | 
					 | 
				
			||||||
            <span class="form-control">
 | 
					 | 
				
			||||||
                {{ analysis.scores.mean|round(2) }} %
 | 
					 | 
				
			||||||
            </span>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <div class="input-group mb-3">
 | 
					 | 
				
			||||||
            <span class="input-group-text">Standard Deviation</span>
 | 
					 | 
				
			||||||
            <span class="form-control">
 | 
					 | 
				
			||||||
                {{ analysis.scores.stdev|round(2) }}
 | 
					 | 
				
			||||||
            </span>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <div class="input-group mb-3">
 | 
					 | 
				
			||||||
            <span class="input-group-text">Median Score</span>
 | 
					 | 
				
			||||||
            <span class="form-control">
 | 
					 | 
				
			||||||
                {{ analysis.scores.median|round(2) }} %
 | 
					 | 
				
			||||||
            </span>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        {% if type == 'exam' %}
 | 
					 | 
				
			||||||
            <div class="input-group mb-3">
 | 
					            <div class="input-group mb-3">
 | 
				
			||||||
                <span class="input-group-text">Dataset Name</span>
 | 
					                <span class="input-group-text">Dataset Name</span>
 | 
				
			||||||
                <span class="form-control">
 | 
					                <span class="form-control">
 | 
				
			||||||
                    {{ dataset.get_name() }}
 | 
					                    {{ dataset.get_name() }}
 | 
				
			||||||
                </span>
 | 
					                </span>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="input-group mb-3">
 | 
				
			||||||
 | 
					                <span class="input-group-text">Author</span>
 | 
				
			||||||
 | 
					                <span class="form-control">
 | 
				
			||||||
 | 
					                    {{ dataset.creator.get_username() }}
 | 
				
			||||||
 | 
					                </span>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="input-group mb-3">
 | 
				
			||||||
 | 
					                <span class="input-group-text">Last Updated</span>
 | 
				
			||||||
 | 
					                <span class="form-control">
 | 
				
			||||||
 | 
					                    {{ dataset.date.strftime('%d %b %Y %H:%M') }}
 | 
				
			||||||
 | 
					                </span>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            {% if dataset.default %}
 | 
				
			||||||
 | 
					                <div class="input-group mb-3">
 | 
				
			||||||
 | 
					                    <span class="input-group-text">
 | 
				
			||||||
 | 
					                        <input type="checkbox" aria-label="Default" class="dataset-default" checked disabled>
 | 
				
			||||||
 | 
					                    </span>
 | 
				
			||||||
 | 
					                    <span class="form-control">
 | 
				
			||||||
 | 
					                        Default Dataset
 | 
				
			||||||
 | 
					                    </select>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
            {% endif %}
 | 
					            {% endif %}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="container">
 | 
					        <div class="container">
 | 
				
			||||||
@@ -132,6 +107,7 @@
 | 
				
			|||||||
                </tbody>
 | 
					                </tbody>
 | 
				
			||||||
            </table>
 | 
					            </table>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block script %}
 | 
					{% block script %}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,7 +43,7 @@ def _fetch_questions():
 | 
				
			|||||||
    data_path = dataset.get_file()
 | 
					    data_path = dataset.get_file()
 | 
				
			||||||
    with open(data_path, 'r') as data_file:
 | 
					    with open(data_path, 'r') as data_file:
 | 
				
			||||||
        data = loads(data_file.read())
 | 
					        data = loads(data_file.read())
 | 
				
			||||||
    questions = generate_questions(data)
 | 
					    questions = generate_questions(dataset=data, randomise=False)
 | 
				
			||||||
    return jsonify({
 | 
					    return jsonify({
 | 
				
			||||||
        'time_limit': end_time,
 | 
					        'time_limit': end_time,
 | 
				
			||||||
        'questions': questions,
 | 
					        'questions': questions,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -41,7 +41,7 @@ class User(UserMixin, db.Model):
 | 
				
			|||||||
    def set_password(self): raise AttributeError('set_password is not a readable attribute.')
 | 
					    def set_password(self): raise AttributeError('set_password is not a readable attribute.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    set_password.setter
 | 
					    set_password.setter
 | 
				
			||||||
    def set_password(self, password:str): self.password = generate_password_hash(password, method="sha256")
 | 
					    def set_password(self, password:str): self.password = generate_password_hash(password, method="scrypt")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def verify_password(self, password:str): return check_password_hash(self.password, password)
 | 
					    def verify_password(self, password:str): return check_password_hash(self.password, password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,13 +3,36 @@
 | 
				
			|||||||
{% block content %}
 | 
					{% block content %}
 | 
				
			||||||
    <div class="instruction-container">
 | 
					    <div class="instruction-container">
 | 
				
			||||||
        <h3>Instructions</h3>
 | 
					        <h3>Instructions</h3>
 | 
				
			||||||
 | 
					        <p>
 | 
				
			||||||
 | 
					            Thank you for putting yourself forward to sit the SKA Referee Theory Exam. Please read the following instructions carefully.
 | 
				
			||||||
 | 
					        </p>
 | 
				
			||||||
 | 
					        <h4>
 | 
				
			||||||
 | 
					            Taking the Exam
 | 
				
			||||||
 | 
					        </h4>
 | 
				
			||||||
        <ul>
 | 
					        <ul>
 | 
				
			||||||
            <li>
 | 
					            <li>
 | 
				
			||||||
                The exam comprises 100 multiple-choice questions.
 | 
					                The exam consists of 100 questions, all of them multiple choice with two or three options, which are designed to test your knowledge of a wide range of rules. For each question, answer what decision you would give as a referee unless the question instructs otherwise.
 | 
				
			||||||
            </li>
 | 
					            </li>
 | 
				
			||||||
            <li>
 | 
					            <li>
 | 
				
			||||||
                For each question, answer what decision you would give as a referee unless the question instructs otherwise.
 | 
					                It should take around an hour to complete.
 | 
				
			||||||
            </li>
 | 
					            </li>
 | 
				
			||||||
 | 
					            <li>
 | 
				
			||||||
 | 
					                The exam should be taken under exam conditions. Materials such as the official rules, guidelines, revision resources, or similar should not be consulted during the test.
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					            <li>
 | 
				
			||||||
 | 
					                We would remind candidates that whilst we are relying on your honesty in this test, your theory knowledge will make up a part of the practical assessment when you are observed refereeing a game.
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					            <li>
 | 
				
			||||||
 | 
					                You also may not discuss the test with any other person while you are sitting it. 
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					            <li>
 | 
				
			||||||
 | 
					                If you have any queries before the exam or would like further feedback on the test, your emails are welcome.
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					        </ul>
 | 
				
			||||||
 | 
					        <h4>
 | 
				
			||||||
 | 
					            Using the Web App
 | 
				
			||||||
 | 
					        </h4>
 | 
				
			||||||
 | 
					        <ul>
 | 
				
			||||||
            <li>
 | 
					            <li>
 | 
				
			||||||
                You will be able to customise the display settings of the exam from the settings panel by clicking on the red gear button <a class="btn btn-danger" aria-title="Settings" title="Settings" onclick="return false;"><i class="bi bi-gear-fill"></i></a>.
 | 
					                You will be able to customise the display settings of the exam from the settings panel by clicking on the red gear button <a class="btn btn-danger" aria-title="Settings" title="Settings" onclick="return false;"><i class="bi bi-gear-fill"></i></a>.
 | 
				
			||||||
            </li>
 | 
					            </li>
 | 
				
			||||||
@@ -17,7 +40,7 @@
 | 
				
			|||||||
                You can view your progress at a glance, as well as navigate to any question in the quiz, using the question grid, accessed via the yellow grid button <a class="btn btn-warning" aria-title="Question Grid" title="Question Grid" onclick="return false;"><i class="bi bi-table"></i></a>.
 | 
					                You can view your progress at a glance, as well as navigate to any question in the quiz, using the question grid, accessed via the yellow grid button <a class="btn btn-warning" aria-title="Question Grid" title="Question Grid" onclick="return false;"><i class="bi bi-table"></i></a>.
 | 
				
			||||||
            </li>
 | 
					            </li>
 | 
				
			||||||
            <li>
 | 
					            <li>
 | 
				
			||||||
                If you are unsure of the answer to a question or would like to revise a question, you can flag the question to review it later on using the flag button button <a class="btn btn-secondary" id="q-nav-flag" title="Flag Button." onclick="return false;"><i class="bi bi-flag-fill"></i></a>.
 | 
					                If you are unsure of the answer to a question or would like to return to a question later, you can flag the question using the flag button button <a class="btn btn-secondary" id="q-nav-flag" title="Flag Button." onclick="return false;"><i class="bi bi-flag-fill"></i></a> to serve as a reminder for you to come back to it later.
 | 
				
			||||||
            </li>
 | 
					            </li>
 | 
				
			||||||
        </ul>
 | 
					        </ul>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
@@ -46,7 +69,7 @@
 | 
				
			|||||||
            Results
 | 
					            Results
 | 
				
			||||||
        </h4>
 | 
					        </h4>
 | 
				
			||||||
        <p>
 | 
					        <p>
 | 
				
			||||||
            The results of your exam will be processed immediately and sent to the SKA Refereeing Coordinator. You will also be emailed a copy of your results.
 | 
					            The results of your exam will be processed immediately and sent to the SKA Refereeing Coordinator. You will also be emailed a copy of your results. If you do not receive an email, make sure to check your spam folder.
 | 
				
			||||||
        </p>
 | 
					        </p>
 | 
				
			||||||
        <p>
 | 
					        <p>
 | 
				
			||||||
            When you are ready to begin the quiz, click the following button.
 | 
					            When you are ready to begin the quiz, click the following button.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,6 +30,7 @@ def _instructions():
 | 
				
			|||||||
@quiz.route('/start/', methods=['GET', 'POST'])
 | 
					@quiz.route('/start/', methods=['GET', 'POST'])
 | 
				
			||||||
def _start():
 | 
					def _start():
 | 
				
			||||||
    clubs = [
 | 
					    clubs = [
 | 
				
			||||||
 | 
					        'Barrowland Bears Korfball Club',
 | 
				
			||||||
        'Dundee Korfball Club',
 | 
					        'Dundee Korfball Club',
 | 
				
			||||||
        'Edinburgh City Korfball Club',
 | 
					        'Edinburgh City Korfball Club',
 | 
				
			||||||
        'Edinburgh Mavericks Korfball Club',
 | 
					        'Edinburgh Mavericks Korfball Club',
 | 
				
			||||||
@@ -59,11 +60,11 @@ def _start():
 | 
				
			|||||||
            except Exception as exception:
 | 
					            except Exception as exception:
 | 
				
			||||||
                write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
 | 
					                write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
 | 
				
			||||||
                return abort(500)
 | 
					                return abort(500)
 | 
				
			||||||
 | 
					            if not test:  return jsonify({'error': 'The exam code you entered is invalid.'}), 400
 | 
				
			||||||
            entry.test = test
 | 
					            entry.test = test
 | 
				
			||||||
            entry.dataset = test.dataset
 | 
					            entry.dataset = test.dataset
 | 
				
			||||||
            entry.user_code = request.form.get('user_code')
 | 
					            entry.user_code = request.form.get('user_code')
 | 
				
			||||||
            entry.user_code = None if entry.user_code == '' else entry.user_code.lower()
 | 
					            entry.user_code = None if entry.user_code == '' else entry.user_code.lower()
 | 
				
			||||||
            if not test:  return jsonify({'error': 'The exam code you entered is invalid.'}), 400
 | 
					 | 
				
			||||||
            if entry.user_code and entry.user_code not in test.adjustments: return jsonify({'error': f'The user code you entered is not valid.'}), 400
 | 
					            if entry.user_code and entry.user_code not in test.adjustments: return jsonify({'error': f'The user code you entered is not valid.'}), 400
 | 
				
			||||||
            if test.end_date < datetime.now(): return jsonify({'error': f'The exam code you entered expired on {test["expiry_date"].strftime("%d %b %Y %H:%M")}.'}), 400
 | 
					            if test.end_date < datetime.now(): return jsonify({'error': f'The exam code you entered expired on {test["expiry_date"].strftime("%d %b %Y %H:%M")}.'}), 400
 | 
				
			||||||
            if test.start_date > datetime.now(): return jsonify({'error': f'The exam has not yet opened. Your exam code will be valid from {test["start_date"].strftime("%d %b %Y %H:%M")}.'}), 400
 | 
					            if test.start_date > datetime.now(): return jsonify({'error': f'The exam has not yet opened. Your exam code will be valid from {test["start_date"].strftime("%d %b %Y %H:%M")}.'}), 400
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,33 +1,33 @@
 | 
				
			|||||||
blinker==1.5
 | 
					blinker==1.9.0
 | 
				
			||||||
cffi==1.15.1
 | 
					cffi==2.0.0
 | 
				
			||||||
click==8.1.3
 | 
					click==8.3.0
 | 
				
			||||||
cryptography==39.0.2
 | 
					cryptography==46.0.2
 | 
				
			||||||
dnspython==2.3.0
 | 
					dnspython==2.8.0
 | 
				
			||||||
dominate==2.7.0
 | 
					dominate==2.9.1
 | 
				
			||||||
email-validator==1.3.1
 | 
					email-validator==2.3.0
 | 
				
			||||||
Flask==2.2.3
 | 
					Flask==3.1.2
 | 
				
			||||||
Flask-Bootstrap==3.3.7.1
 | 
					Flask-Bootstrap==3.3.7.1
 | 
				
			||||||
Flask-Login==0.6.2
 | 
					Flask-Login==0.6.3
 | 
				
			||||||
Flask-Mail==0.9.1
 | 
					Flask-Mail==0.10.0
 | 
				
			||||||
Flask-SQLAlchemy==3.0.3
 | 
					Flask-SQLAlchemy==3.1.1
 | 
				
			||||||
Flask-WTF==1.1.1
 | 
					Flask-WTF==1.2.2
 | 
				
			||||||
greenlet==2.0.2
 | 
					greenlet==3.2.4
 | 
				
			||||||
gunicorn==20.1.0
 | 
					gunicorn==23.0.0
 | 
				
			||||||
idna==3.4
 | 
					idna==3.10
 | 
				
			||||||
itsdangerous==2.1.2
 | 
					itsdangerous==2.2.0
 | 
				
			||||||
Jinja2==3.1.2
 | 
					Jinja2==3.1.6
 | 
				
			||||||
MarkupSafe==2.1.2
 | 
					MarkupSafe==3.0.3
 | 
				
			||||||
pip==23.0.1
 | 
					packaging==25.0
 | 
				
			||||||
pycparser==2.21
 | 
					pycparser==2.23
 | 
				
			||||||
PyMySQL==1.0.2
 | 
					PyMySQL==1.1.2
 | 
				
			||||||
python-dotenv==1.0.0
 | 
					python-dotenv==1.1.1
 | 
				
			||||||
setuptools==67.4.0
 | 
					setuptools==80.9.0
 | 
				
			||||||
six==1.16.0
 | 
					six==1.17.0
 | 
				
			||||||
SQLAlchemy==2.0.4
 | 
					SQLAlchemy==2.0.43
 | 
				
			||||||
sqlalchemy-json==0.5.0
 | 
					sqlalchemy-json==0.7.0
 | 
				
			||||||
SQLAlchemy-Utils==0.40.0
 | 
					SQLAlchemy-Utils==0.42.0
 | 
				
			||||||
typing_extensions==4.5.0
 | 
					typing_extensions==4.15.0
 | 
				
			||||||
visitor==0.1.3
 | 
					visitor==0.1.3
 | 
				
			||||||
Werkzeug==2.2.3
 | 
					Werkzeug==3.1.3
 | 
				
			||||||
wheel==0.38.4
 | 
					wheel==0.45.1
 | 
				
			||||||
WTForms==3.0.1
 | 
					WTForms==3.2.1
 | 
				
			||||||
		Reference in New Issue
	
	Block a user