├── .gitignore ├── LICENSE ├── README.md ├── docs ├── Final_proposal.pdf ├── GSOC_FINAL_REPORT.md ├── Screeshots_Label_buddy │ ├── Annotation_page_after_annotation.png │ ├── Annotation_page_after_review.png │ ├── Annotation_page_before_annotation.png │ ├── Edit_profile.png │ ├── Export_data_page.png │ ├── Import_data.png │ ├── Index_page.png │ ├── List_annotations_page.png │ ├── Login.png │ ├── Project_page_after_import.png │ ├── Project_page_before_import.png │ ├── Register.png │ ├── Review_page_after_review.png │ ├── Review_page_before_review.png │ ├── project_create_page.png │ └── project_edit_page.png └── user_manual.md ├── label_buddy ├── label_buddy │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── manage.py ├── media │ ├── audio │ │ └── .gitignore │ └── images │ │ └── .gitignore ├── projects │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── forms.py │ ├── helpers.py │ ├── migrations │ │ └── .gitignore │ ├── models.py │ ├── permissions.py │ ├── serializers.py │ ├── urls.py │ └── views.py ├── static │ ├── .gitignore │ ├── css │ │ ├── annotation_page.css │ │ ├── common.css │ │ ├── delete_annotation.css │ │ ├── delete_task.css │ │ ├── index.css │ │ ├── list_annotations.css │ │ ├── password_reset.css │ │ ├── project_page.css │ │ ├── review_page.css │ │ ├── signin_page.css │ │ ├── signup_page.css │ │ └── welcome_page.css │ ├── images │ │ ├── favicons │ │ │ ├── Label_buddy.png │ │ │ ├── facebook_cover_photo_1.png │ │ │ ├── facebook_cover_photo_2.png │ │ │ ├── facebook_profile_image.png │ │ │ ├── favicon.ico │ │ │ ├── favicon.png │ │ │ ├── linkedin_banner_image_1.png │ │ │ ├── linkedin_banner_image_2.png │ │ │ ├── linkedin_profile_image.png │ │ │ ├── logo.png │ │ │ ├── logo_transparent.png │ │ │ ├── logo_transparent_165x25.png │ │ │ ├── logo_transparent_resized.png │ │ │ ├── new_logo.svg │ │ │ ├── pinterest_board_photo.png │ │ │ ├── pinterest_profile_image.png │ │ │ ├── twitter_header_photo_1.png │ │ │ ├── twitter_header_photo_2.png │ │ │ ├── twitter_profile_image.png │ │ │ └── youtube_profile_image.png │ │ ├── project_logo.png │ │ └── user │ │ │ └── user.jpg │ └── js │ │ ├── annotation_page.js │ │ ├── common.js │ │ ├── edit_project.js │ │ ├── index.js │ │ ├── list_annotations.js │ │ ├── project_page.js │ │ ├── review_page.js │ │ ├── signin_page.js │ │ ├── signup_page.js │ │ └── welcome_page.js ├── tasks │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── forms.py │ ├── helpers.py │ ├── migrations │ │ └── .gitignore │ ├── models.py │ ├── serializers.py │ ├── urls.py │ └── views.py ├── templates │ ├── 404.html │ ├── account │ │ ├── account_inactive.html │ │ ├── email.html │ │ ├── email │ │ │ ├── email_confirmation_message.txt │ │ │ ├── email_confirmation_signup_message.txt │ │ │ ├── email_confirmation_signup_subject.txt │ │ │ ├── email_confirmation_subject.txt │ │ │ ├── password_reset_key_message.txt │ │ │ └── password_reset_key_subject.txt │ │ ├── email_confirm.html │ │ ├── login.html │ │ ├── logout.html │ │ ├── messages │ │ │ ├── cannot_delete_primary_email.txt │ │ │ ├── email_confirmation_sent.txt │ │ │ ├── email_confirmed.txt │ │ │ ├── email_deleted.txt │ │ │ ├── logged_in.txt │ │ │ ├── logged_out.txt │ │ │ ├── password_changed.txt │ │ │ ├── password_set.txt │ │ │ ├── primary_email_set.txt │ │ │ └── unverified_primary_email.txt │ │ ├── password_change.html │ │ ├── password_reset.html │ │ ├── password_reset_done.html │ │ ├── password_reset_from_key.html │ │ ├── password_reset_from_key_done.html │ │ ├── password_set.html │ │ ├── signup.html │ │ ├── signup_closed.html │ │ ├── snippets │ │ │ └── already_logged_in.html │ │ ├── verification_sent.html │ │ └── verified_email_required.html │ ├── annotation_page_modal.html │ ├── base.html │ ├── export_modal.html │ ├── import_modal.html │ ├── index_modal.html │ ├── label_buddy │ │ ├── annotation_page.html │ │ ├── create_project.html │ │ ├── delete_annotation.html │ │ ├── delete_project.html │ │ ├── delete_task.html │ │ ├── edit_project.html │ │ ├── index.html │ │ ├── list_annotations.html │ │ ├── project_page.html │ │ ├── review_page.html │ │ ├── user_edit_profile.html │ │ └── welcome_page.html │ ├── list_annotations_filters.html │ ├── pagination.html │ ├── project_information_modal.html │ ├── project_modals.html │ ├── project_page_filters.html │ ├── review_info_modal.html │ └── review_page_modal.html ├── tests │ ├── __init__.py │ └── test_pages.py └── users │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── forms.py │ ├── migrations │ └── .gitignore │ ├── models.py │ ├── password_validators.py │ ├── serializers.py │ ├── urls.py │ └── views.py ├── pyvenv.cfg └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # virtual enviroment folders 2 | bin/ 3 | lib/ 4 | share/ 5 | env/ 6 | 7 | 8 | *.pyc 9 | label_buddy/*.png 10 | extract_folder/ 11 | 12 | # local settings and db ignonred 13 | label_buddy/.coverage 14 | label_buddy/db.sqlite3 15 | label_buddy/label_buddy/local_settings.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 GFOSS - Open Technologies Alliance (Οργανισμός Ανοιχτών Τεχνολογιών - EEΛΛΑΚ) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Google Summer of Code with GFOSS :sun_with_face: 2 | 3 | **Project:** Creation of a multi user audio first annotation tool 4 | 5 | **Mentors:** Pantelis Vikatos, Markos Gogoulos 6 | 7 | **Student:** Ioannis Sina 8 | 9 | # Introduction 10 | 11 | An annotation tool helps people (without the need for specific knowledge) to mark a segment of an audio file (waveform), an image or text etc. in order to specify the segment’s properties. Annotation tools are used in machine learning applications such as Natural Language Processing (NLP) and Object Detection in order to train machines to identify objects or text. While there is a variety of annotation tools, most of them lack the multi-user feature (multiple users annotating a single project simultaneously) whose implementation is planned in this project. The audio annotation process is usually tedious and time consuming therefore, these tools (annotation tools which provide the multi-user feature) are necessary in order to reduce the effort needed as well as to enhance the quality of annotations. Since in most tasks related to audio classification, speech recognition, music detection etc., machine and deep learning models are trained and evaluated with the use audio that has previously been annotated by humans, the implementation of such a tool will lead to higher accuracy of annotated files, as they will have been annotated by more than one human, providing a more reliable dataset. In effect, multi-user annotation will reduce the possibility of human error e.g. an occasional mistaken labelling of a segment might be pointed out by another annotator. 12 | 13 | **Already existing annotation tools:** 14 | 15 | Label Studio: https://github.com/heartexlabs/label-studio 16 | 17 | BAT annotation tool: https://github.com/BlaiMelendezCatalan/BAT 18 | 19 | Computer Vision Annotation Tool (CVAT): https://github.com/openvinotoolkit/cvat 20 | 21 | # Project goals :dart: 22 | 23 | We will try to develop a web application (audio annotation tool) which will provide the multi-user feature and a pleasant UI for users. There will be three distinct types of users in the application: Managers, Annotators and Reviewers. 24 | 25 | **Managers will:** 26 | 27 | * Create projects and edit their configurations (title, annotators for each project etc.) 28 | * Upload audio files (links, csv of links or local files) for each project created 29 | * Add/edit labels (labels are used by annotators in order to mark segments of an audio file) 30 | * Assign roles to users (specify who will be annotator or reviewer) 31 | * Export annotations in JSON or CSV format 32 | 33 | **Reviewers will:** 34 | 35 | * See all annotations done by annotators 36 | * Set annotations as approved or rejected 37 | * Add comments to annotations done 38 | 39 | **Annotators will:** 40 | 41 | * Annotate audio files (tasks) assigned to them (by managers) 42 | * Update annotations 43 | * Submit tasks as annotated 44 | 45 | # Steps to run 46 | 47 | Clone repository and cd to the folder 48 | ~~~ 49 | git clone https://github.com/eellak/gsoc2021-audio-annotation-tool/ 50 | cd gsoc2021-audio-annotation-tool 51 | ~~~ 52 | 53 | Create virtual enviroment 54 | ~~~ 55 | python3 -m venv env 56 | ~~~ 57 | 58 | Activate it for **Linux** 59 | ~~~ 60 | source env/bin/activate 61 | ~~~ 62 | 63 | Activate it for **Windows** 64 | ~~~ 65 | env\Scripts\activate 66 | ~~~ 67 | 68 | Install requirements and cd to label_buddy/ 69 | ~~~ 70 | pip install -r requirements.txt 71 | cd label_buddy 72 | ~~~ 73 | 74 | Make migrations for the Database and all dependencies 75 | ~~~ 76 | python manage.py makemigrations users projects tasks 77 | python manage.py migrate 78 | ~~~ 79 | 80 | After the above process create a super user and run server 81 | ~~~ 82 | python manage.py createsuperuser 83 | python manage.py runserver 84 | ~~~ 85 | 86 | Visit http://localhost:8000/admin, navigate to users/[your user] and set can_create_projects to true so you can start creating projects. 87 | 88 | Visit https://labelbuddy.io/ and sign with the following credentials: 89 | 90 | - **Username**: demo 91 | - **Password**: labelbuddy123 92 | 93 | in order to create projects, upload files and annotate them. 94 | -------------------------------------------------------------------------------- /docs/Final_proposal.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/docs/Final_proposal.pdf -------------------------------------------------------------------------------- /docs/GSOC_FINAL_REPORT.md: -------------------------------------------------------------------------------- 1 | # :information_source: Project info 2 | 3 | ![facebook_cover_photo_1](https://user-images.githubusercontent.com/49285637/130322348-a232336f-c4cf-4c1e-8c77-b7e5a27fa108.png) 4 | 5 | ### Brief explanation 6 | **Label Buddy** is powered by [GFOSS](https://gfoss.eu/) and [GSoC](https://summerofcode.withgoogle.com/). It is an audio labeling tool which features 3 types of users: Managers, annotators and reviewers. The distinction between the user roles is what makes Label Buddy unique. A Detailed explanation of Label Buddy and steps for installation can be found [here](https://github.com/eellak/gsoc2021-audio-annotation-tool/blob/main/README.md). 7 | 8 | ### Important links 9 | * GitHub repository: https://github.com/eellak/gsoc2021-audio-annotation-tool 10 | * GitHub README link: https://github.com/eellak/gsoc2021-audio-annotation-tool/blob/main/README.md 11 | * Label Buddy video demo: https://www.youtube.com/watch?v=ikPrWbs9MWw 12 | * Label Buddy demo link: Go to https://labelbuddy.io/ and sign in with the following credentials in order to check out Label Buddy: 13 | * **Username**: demo 14 | * **Password**: labelbuddy123 15 | 16 | [**Here**](https://github.com/eellak/gsoc2021-audio-annotation-tool/blob/main/docs/user_manual.md) you will find a user manual with screenshots and explanations for Label Buddy 17 | 18 | ### Important pull requests 19 | * Merge dev_branch with main: [#25](https://github.com/eellak/gsoc2021-audio-annotation-tool/pull/25) 20 | * Merge review_page with main: [#31](https://github.com/eellak/gsoc2021-audio-annotation-tool/pull/31) 21 | 22 | ### Tech Stack 23 | * Django, Django templates, Python 24 | * HTML, css, js 25 | 26 | 27 | # :heavy_check_mark: Tasks completed during GSoC (7 JUNE - 16 AUGUST) 28 | [Here](https://github.com/eellak/gsoc2021-audio-annotation-tool/issues?q=is%3Aissue+is%3Aclosed) you can see all issues closed during the coding period. Each issue has a brief explanation of what should be done. In every issue you can see every commit done for it. Now let's list the **most important** tasks (issues) for Label Buddy. 29 | 30 | ## 1. Create django models 31 | * Related issues: [#1](https://github.com/eellak/gsoc2021-audio-annotation-tool/issues/1), [#2](https://github.com/eellak/gsoc2021-audio-annotation-tool/issues/2), [#3](https://github.com/eellak/gsoc2021-audio-annotation-tool/issues/3) 32 | * Working branch: [main](https://github.com/eellak/gsoc2021-audio-annotation-tool) 33 | * **Task**: Created Django models: User, Project, Task, Annotation, Comment, Label. Users will be managers, annotators and reviewers. If can_create_projects variable is set to true user can create projects, add labels and import tasks (audio files). Annotators can create annotations for tasks and reviewers can create reviews for completed annotations 34 | 35 | ## 2. Login/Register 36 | * Related issue: [#12](https://github.com/eellak/gsoc2021-audio-annotation-tool/issues/12) 37 | * Working branch: [dev_branch](https://github.com/eellak/gsoc2021-audio-annotation-tool/tree/dev_branch) 38 | * **Task**: Created basic Login/Register system. For the backend of this task I used [django-allauth](https://django-allauth.readthedocs.io/en/latest/installation.html) 39 | 40 | ## 3. Index page 41 | * Related issue: [#13](https://github.com/eellak/gsoc2021-audio-annotation-tool/issues/13) 42 | * Working branch: [dev_branch](https://github.com/eellak/gsoc2021-audio-annotation-tool/tree/dev_branch) 43 | * **Task**: Create index page in which the user will be able to see all projects that he/she is involved in. A user is involved in a project if he/she is a manager an annotator or a reviewer 44 | 45 | ## 4. User creates project 46 | * Related issue: [#14](https://github.com/eellak/gsoc2021-audio-annotation-tool/issues/14) 47 | * Working branch: [dev_branch](https://github.com/eellak/gsoc2021-audio-annotation-tool/tree/dev_branch) 48 | * **Task**: Create a page for creating projects (if user has the permission) . User can specify all project's attributes like title, labels, annotators etc 49 | 50 | ## 5. User edits project 51 | * Related issue: [#15](https://github.com/eellak/gsoc2021-audio-annotation-tool/issues/15) 52 | * Working branch: [dev_branch](https://github.com/eellak/gsoc2021-audio-annotation-tool/tree/dev_branch) 53 | * **Task**: Create a page for editing projects. **Only** managers of a project can edit it 54 | 55 | ## 6. Add tasks to project 56 | * Related issue: [#16](https://github.com/eellak/gsoc2021-audio-annotation-tool/issues/16) 57 | * Working branch: [dev_branch](https://github.com/eellak/gsoc2021-audio-annotation-tool/tree/dev_branch) 58 | * **Task**: Managers can import single audio files (.wav, .mp3 and .mp4) or .zip files containing accepted format files. Every audio file represent a task for the project which needs to be annotated 59 | 60 | ## 7. List tasks for project 61 | * Related issue: [#17](https://github.com/eellak/gsoc2021-audio-annotation-tool/issues/17) 62 | * Working branch: [dev_branch](https://github.com/eellak/gsoc2021-audio-annotation-tool/tree/dev_branch) 63 | * **Task**: Create a page for listing tasks for annotation/review. Tasks displayed depend on the user's role in the project. Managers can see all task uploaded. Annotators can see only tasks assigned to them and reviewers can see all tasks in order to review them 64 | 65 | ## 8. Annotate tasks for project 66 | * Related issue: [#18](https://github.com/eellak/gsoc2021-audio-annotation-tool/issues/18) 67 | * Working branch: [dev_branch](https://github.com/eellak/gsoc2021-audio-annotation-tool/tree/dev_branch) 68 | * **Task**: Create annotation page where annotators will be able to annotate a task. They are able to drug and create regions on the waveform and label them with the project's labels 69 | 70 | ## 9. Review tasks for project 71 | * Related issue: [#19](https://github.com/eellak/gsoc2021-audio-annotation-tool/issues/19) 72 | * Working branch: [review_page](https://github.com/eellak/gsoc2021-audio-annotation-tool/tree/review_page) 73 | * **Task**: Create review page where a reviewer can comment an annotation and either Approve it or Reject it 74 | 75 | ## 10. Export annotations for project 76 | * Related issue: [#20](https://github.com/eellak/gsoc2021-audio-annotation-tool/issues/20) 77 | * Working branch: [dev_branch](https://github.com/eellak/gsoc2021-audio-annotation-tool/tree/dev_branch) 78 | * **Task**: Managers are able to export all annotations completed for a project in JSON or CSV format. They can export either Approved annotations or all of them 79 | * Examples of exported files can be found [here](https://drive.google.com/drive/folders/1aQqYuO4czitTEFcDNjBsqc99zewMeW2r?usp=sharing) 80 | 81 | # :white_check_mark: Task Left to Do 82 | * UX imporvements: https://www.figma.com/file/gUelQicOdfJkrqs2XJvHl5/Label-Buddy?node-id=214%3A2605 83 | * Rar file upload. Zip is already accepted 84 | * Swagger documentation 85 | * Use exported data for machine learning models 86 | 87 | # :blush: Acknowledgements 88 | 89 | First and foremost, I want to thank my mentors [**Pantelis Vikatos**](https://www.linkedin.com/in/pantelis-vikatos-1383437a/) and [**Markos Gogoulos**](https://github.com/mgogoulos), for guiding, giving me useful advice for the project and helping me whenever I faced an issue. It was a pleasure working with them and I hope I get the opportunity to do it again soon. We had a meeting twice a week and our means of communication was **[Mattermost](https://mattermost.com/)**. Moreover I would like to thank **Jen Anastasopoulou** and **Lina Aggelopoulou** for the UX improvements which can be found [here](https://www.figma.com/file/gUelQicOdfJkrqs2XJvHl5/Label-Buddy?node-id=214%3A2605). These improvements will have been applied by the end of the month and will make Label Buddy more user friendly. Finally, I would like to express my appreciation to **[Orfium](https://www.orfium.com/)** for supporting the whole project from the start to the end of it. 90 | -------------------------------------------------------------------------------- /docs/Screeshots_Label_buddy/Annotation_page_after_annotation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/docs/Screeshots_Label_buddy/Annotation_page_after_annotation.png -------------------------------------------------------------------------------- /docs/Screeshots_Label_buddy/Annotation_page_after_review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/docs/Screeshots_Label_buddy/Annotation_page_after_review.png -------------------------------------------------------------------------------- /docs/Screeshots_Label_buddy/Annotation_page_before_annotation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/docs/Screeshots_Label_buddy/Annotation_page_before_annotation.png -------------------------------------------------------------------------------- /docs/Screeshots_Label_buddy/Edit_profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/docs/Screeshots_Label_buddy/Edit_profile.png -------------------------------------------------------------------------------- /docs/Screeshots_Label_buddy/Export_data_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/docs/Screeshots_Label_buddy/Export_data_page.png -------------------------------------------------------------------------------- /docs/Screeshots_Label_buddy/Import_data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/docs/Screeshots_Label_buddy/Import_data.png -------------------------------------------------------------------------------- /docs/Screeshots_Label_buddy/Index_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/docs/Screeshots_Label_buddy/Index_page.png -------------------------------------------------------------------------------- /docs/Screeshots_Label_buddy/List_annotations_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/docs/Screeshots_Label_buddy/List_annotations_page.png -------------------------------------------------------------------------------- /docs/Screeshots_Label_buddy/Login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/docs/Screeshots_Label_buddy/Login.png -------------------------------------------------------------------------------- /docs/Screeshots_Label_buddy/Project_page_after_import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/docs/Screeshots_Label_buddy/Project_page_after_import.png -------------------------------------------------------------------------------- /docs/Screeshots_Label_buddy/Project_page_before_import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/docs/Screeshots_Label_buddy/Project_page_before_import.png -------------------------------------------------------------------------------- /docs/Screeshots_Label_buddy/Register.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/docs/Screeshots_Label_buddy/Register.png -------------------------------------------------------------------------------- /docs/Screeshots_Label_buddy/Review_page_after_review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/docs/Screeshots_Label_buddy/Review_page_after_review.png -------------------------------------------------------------------------------- /docs/Screeshots_Label_buddy/Review_page_before_review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/docs/Screeshots_Label_buddy/Review_page_before_review.png -------------------------------------------------------------------------------- /docs/Screeshots_Label_buddy/project_create_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/docs/Screeshots_Label_buddy/project_create_page.png -------------------------------------------------------------------------------- /docs/Screeshots_Label_buddy/project_edit_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/docs/Screeshots_Label_buddy/project_edit_page.png -------------------------------------------------------------------------------- /docs/user_manual.md: -------------------------------------------------------------------------------- 1 | # User manual 2 | 3 | User manual contains screenshots from Label Buddy. A brief explanation is given for every screenshot. 4 | 5 | ## 1. Login/Register 6 | Users can create an account for Label Buddy and Log in 7 | 8 | #### Login page 9 | ![Login](https://user-images.githubusercontent.com/49285637/131688949-f7dbed17-7a44-49e1-9ea0-ffcead2563d4.png) 10 | 11 | #### Register page 12 | ![Register](https://user-images.githubusercontent.com/49285637/131688784-83ff24ad-053a-4936-b7fd-14c68181a9f8.png) 13 | 14 | ## 2. Index page 15 | Users will be able to see all projects that they are involved in. A user is involved in a project if he/she is a manager an annotator or a reviewer 16 | 17 | #### Index page 18 | ![Index_page](https://user-images.githubusercontent.com/49285637/131689398-fa1e1e25-c3f1-4aeb-9d6c-6c285af48971.png) 19 | 20 | ## 3. User creates project 21 | Users can (if they have the permission) create projects. User can specify all project's attributes like title, labels, annotators etc 22 | 23 | #### Project create page 24 | ![project_create_page](https://user-images.githubusercontent.com/49285637/130134829-d6f51827-b440-4ca9-bd9e-56a9617f1c18.png) 25 | 26 | ## 4. User edits project 27 | Users **who are managers** for the project can edit it 28 | 29 | #### Project edit page 30 | ![project_edit_page](https://user-images.githubusercontent.com/49285637/130135153-910841f5-8bb8-4f72-96f7-1c199235c2d1.png) 31 | 32 | ## 5. Add tasks to project 33 | Users who are managers for the project can import single audio files (.wav, .mp3 and .mp4) or .zip files containing accepted format files. Every audio file represent a task for the 34 | project which needs to be annotated 35 | 36 | #### Import data page 37 | ![Import_data](https://user-images.githubusercontent.com/49285637/130135692-69e6632e-f46f-4ca1-95a9-ce6b2f1d8cc5.png) 38 | 39 | ## 6. List tasks for project 40 | Users can see tasks for annotation/review. Tasks displayed depend on the user's role in the project. Managers can see all task uploaded. 41 | Annotators can see only tasks assigned to them and reviewers can see all tasks in order to review them 42 | 43 | #### Project page **before** importing data 44 | ![Project_page_before_import](https://user-images.githubusercontent.com/49285637/130136446-1a075f56-1afc-48ef-9fe5-24fd3737cabe.png) 45 | 46 | #### Project page **after** importing 10 audio files 47 | ![Project_page_after_import](https://user-images.githubusercontent.com/49285637/130136484-a8d2f72f-08fd-496e-8ac4-4793af940b21.png) 48 | 49 | ## 7. Annotate tasks for project 50 | Users who are annotators for the project will be able to annotate a task. They are able to drug and create regions on the waveform and label them with the project's labels 51 | 52 | #### Annotation page **before** creating an annotation 53 | ![Annotation_page_before_annotation](https://user-images.githubusercontent.com/49285637/130137165-9de6e903-cb9e-41c7-baf9-50220a9bb3e5.png) 54 | 55 | #### Annotation page **after** creating an annotation 56 | ![Annotation_page_after_annotation](https://user-images.githubusercontent.com/49285637/130137175-052d0cbf-0d70-40cc-9320-7126d7a1a28e.png) 57 | 58 | ## 8. Review tasks for project 59 | Users who are reviewers for the project can comment an annotation and either Approve it or Reject it 60 | 61 | #### Review page **before** creating a review 62 | ![Review_page_before_review](https://user-images.githubusercontent.com/49285637/130137748-c8b8f2f6-1161-4663-8110-4b71a6d00691.png) 63 | 64 | #### Review page **after** creating a review 65 | ![Review_page_after_review](https://user-images.githubusercontent.com/49285637/130137805-0515e932-2dae-43fd-8dbd-f45e179a1cfa.png) 66 | 67 | ## 9. Export annotations for project 68 | Managers are able to export all annotations completed for a project in JSON or CSV format. They can export either Approved annotations or all of them 69 | 70 | #### Export data page 71 | ![Export_data_page](https://user-images.githubusercontent.com/49285637/130138221-b79f7cbf-19a2-4929-a687-b56ae7d86674.png) 72 | 73 | ## Some other pages 74 | 75 | #### List annotations for reviewers to choose 76 | ![List_annotations_page](https://user-images.githubusercontent.com/49285637/130138543-cc8a9a88-e886-4b6b-b8d7-6c30e40d2b37.png) 77 | 78 | #### Annotation page after annotation reviewed 79 | ![Annotation_page_after_review](https://user-images.githubusercontent.com/49285637/130138650-94f9740b-e8dd-40bd-8883-2a1f175d84e0.png) 80 | 81 | #### User edit profile page 82 | ![Edit_profile](https://user-images.githubusercontent.com/49285637/130138844-fad51340-41c3-4b86-913b-24fb538c59d2.png) 83 | 84 | -------------------------------------------------------------------------------- /label_buddy/label_buddy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/label_buddy/__init__.py -------------------------------------------------------------------------------- /label_buddy/label_buddy/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for label_buddy project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'label_buddy.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /label_buddy/label_buddy/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for label_buddy project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.2.3. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.2/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | import os 15 | 16 | # Build paths inside the project like this: BASE_DIR / 'subdir' 17 | BASE_DIR = Path(__file__).resolve().parent.parent 18 | 19 | 20 | # Quick-start development settings - unsuitable for production 21 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ 22 | 23 | # SECURITY WARNING: keep the secret key used in production secret! 24 | SECRET_KEY = "django_key_long_random_key_here_12763523765&^!@$#%%^@#$%@#$" 25 | 26 | # SECURITY WARNING: don't run with debug turned on in production! 27 | DEBUG = True 28 | 29 | ALLOWED_HOSTS = ['67.205.153.230', '127.0.0.1'] 30 | 31 | INTERNAL_IPS = [ 32 | '127.0.0.1', 33 | ] 34 | 35 | # Django-allauth settings 36 | ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = 2 37 | ACCOUNT_EMAIL_REQUIRED = True # Email adress must be verified in ordet to register 38 | ACCOUNT_EMAIL_VERIFICATION = "optional" # Email should be verified in roder to log in 39 | ACCOUNT_LOGIN_ATTEMPTS_LIMIT = 10 # Login attempts in order to prevent brute force attacks 40 | ACCOUNT_LOGIN_ATTEMPTS_TIMEOUT = 5 41 | ACCOUNT_USERNAME_MIN_LENGTH = "4" 42 | ACCOUNT_AUTHENTICATION_METHOD = 'username_email' 43 | ACCOUNT_SIGNUP_PASSWORD_VERIFICATION = False # Confirmation password isnt needed 44 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' # On signup send messages to this email (print in terminal) 45 | ACCOUNT_FORMS = { 46 | 'login': 'users.forms.ExtendedLogInForm', 47 | 'signup': 'users.forms.ExtendedSignUpForm', 48 | 'reset_password': 'users.forms.ExtendedResetPasswordForm', 49 | } 50 | 51 | # Password validation 52 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators 53 | AUTH_PASSWORD_VALIDATORS = [ 54 | { 55 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 56 | }, 57 | { 58 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 59 | 'OPTIONS': { 60 | 'min_length': 8, 61 | }, 62 | }, 63 | { 64 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 65 | }, 66 | { 67 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 68 | }, 69 | { 70 | 'NAME': 'users.password_validators.UppercaseValidator', 71 | }, 72 | { 73 | 'NAME': 'users.password_validators.NumberValidator', 74 | }, 75 | { 76 | 'NAME': 'users.password_validators.SymbolValidator', 77 | }, 78 | ] 79 | 80 | 81 | LOGIN_REDIRECT_URL = "/" 82 | ACCOUNT_LOGOUT_REDIRECT_URL = "/accounts/login/" 83 | MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' 84 | 85 | AUTHENTICATION_BACKENDS = ( 86 | 'django.contrib.auth.backends.ModelBackend', 87 | 'allauth.account.auth_backends.AuthenticationBackend', 88 | ) 89 | 90 | # Application definition 91 | 92 | 93 | INSTALLED_APPS = [ 94 | 'django.contrib.admin', 95 | 'allauth', 96 | 'allauth.account', 97 | 'allauth.socialaccount', 98 | 'django.contrib.auth', 99 | 'django.contrib.sites', 100 | 'django.contrib.contenttypes', 101 | 'django_extensions', 102 | 'django.contrib.sessions', 103 | 'django.contrib.messages', 104 | 'django.contrib.staticfiles', 105 | 'debug_toolbar', 106 | 'rest_framework', 107 | # Third party 108 | 'colorfield', 109 | 'url_or_relative_url_field', 110 | 'crispy_forms', 111 | 112 | # Own 113 | 'users', 114 | 'projects', 115 | 'tasks', 116 | ] 117 | 118 | MIDDLEWARE = [ 119 | 'django.middleware.security.SecurityMiddleware', 120 | 'debug_toolbar.middleware.DebugToolbarMiddleware', 121 | 'django.contrib.sessions.middleware.SessionMiddleware', 122 | 'django.middleware.common.CommonMiddleware', 123 | 'django.middleware.csrf.CsrfViewMiddleware', 124 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 125 | 'django.contrib.messages.middleware.MessageMiddleware', 126 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 127 | ] 128 | 129 | DEBUG_TOOLBAR_PANELS = [ 130 | 'debug_toolbar.panels.history.HistoryPanel', 131 | 'debug_toolbar.panels.versions.VersionsPanel', 132 | 'debug_toolbar.panels.timer.TimerPanel', 133 | 'debug_toolbar.panels.settings.SettingsPanel', 134 | 'debug_toolbar.panels.headers.HeadersPanel', 135 | 'debug_toolbar.panels.request.RequestPanel', 136 | 'debug_toolbar.panels.sql.SQLPanel', 137 | 'debug_toolbar.panels.staticfiles.StaticFilesPanel', 138 | 'debug_toolbar.panels.templates.TemplatesPanel', 139 | 'debug_toolbar.panels.cache.CachePanel', 140 | 'debug_toolbar.panels.signals.SignalsPanel', 141 | 'debug_toolbar.panels.logging.LoggingPanel', 142 | 'debug_toolbar.panels.redirects.RedirectsPanel', 143 | 'debug_toolbar.panels.profiling.ProfilingPanel', 144 | ] 145 | 146 | ROOT_URLCONF = 'label_buddy.urls' 147 | 148 | TEMPLATES = [ 149 | { 150 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 151 | 'DIRS': [os.path.join(BASE_DIR, "templates"), ], 152 | 'APP_DIRS': True, 153 | 'OPTIONS': { 154 | 'context_processors': [ 155 | 'django.template.context_processors.debug', 156 | 'django.template.context_processors.request', 157 | 'django.contrib.auth.context_processors.auth', 158 | 'django.contrib.messages.context_processors.messages', 159 | ], 160 | }, 161 | }, 162 | ] 163 | 164 | WSGI_APPLICATION = 'label_buddy.wsgi.application' 165 | 166 | AUTH_USER_MODEL = "users.User" 167 | 168 | # Database 169 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases 170 | 171 | # To Fix 172 | if True: 173 | DATABASES = { 174 | 'default': { 175 | 'ENGINE': 'django.db.backends.sqlite3', 176 | 'NAME': BASE_DIR / 'db.sqlite3', 177 | } 178 | } 179 | else: 180 | DATABASES = { 181 | 'default': { 182 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 183 | 'NAME': 'label_buddy', 184 | 'USER': 'labelbuddy', 185 | 'PASSWORD': 'labelbuddy123', 186 | 'HOST': 'localhost', 187 | 'PORT': '', 188 | } 189 | } 190 | 191 | # Internationalization 192 | # https://docs.djangoproject.com/en/3.2/topics/i18n/ 193 | LANGUAGE_CODE = 'en-us' 194 | TIME_ZONE = 'Europe/Athens' 195 | 196 | USE_I18N = True 197 | USE_L10N = True 198 | USE_TZ = True 199 | SITE_ID = 1 200 | 201 | # Static files (CSS, JavaScript, Images) 202 | # https://docs.djangoproject.com/en/3.2/howto/static-files/ 203 | 204 | STATIC_URL = "/static/" # where js/css files are stored on the filesystem 205 | 206 | # Base url to serve media files 207 | MEDIA_URL = '/media/' 208 | 209 | # Path where media is stored 210 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media/') 211 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media/') 212 | 213 | STATICFILES_DIRS = [ 214 | os.path.join(BASE_DIR, 'static') 215 | ] 216 | 217 | # Default primary key field type 218 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field 219 | 220 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 221 | CRISPY_TEMPLATE_PACK = 'bootstrap4' 222 | -------------------------------------------------------------------------------- /label_buddy/label_buddy/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | label_buddy URL Configuration 3 | 4 | The `urlpatterns` list routes URLs to views. For more information please see: 5 | https://docs.djangoproject.com/en/3.2/topics/http/urls/ 6 | Examples: 7 | Function views 8 | 1. Add an import: from my_app import views 9 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 10 | Class-based views 11 | 1. Add an import: from other_app.views import Home 12 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 13 | Including another URLconf 14 | 1. Import the include() function: from django.urls import include, path 15 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 16 | """ 17 | 18 | from django.contrib import admin 19 | from django.urls import path 20 | from django.conf.urls import url, include 21 | from django.conf.urls.static import static 22 | from . import settings 23 | import debug_toolbar 24 | 25 | urlpatterns = [ 26 | path('', include('projects.urls')), 27 | path('', include('tasks.urls')), 28 | path('', include('users.urls')), 29 | path('admin/', admin.site.urls), 30 | path('api-auth/', include('rest_framework.urls')), 31 | path('__debug__/', include(debug_toolbar.urls)), 32 | url(r'^accounts/', include('allauth.urls')), 33 | ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 34 | -------------------------------------------------------------------------------- /label_buddy/label_buddy/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for label_buddy project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'label_buddy.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /label_buddy/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'label_buddy.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /label_buddy/media/audio/.gitignore: -------------------------------------------------------------------------------- 1 | # exclude all files 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /label_buddy/media/images/.gitignore: -------------------------------------------------------------------------------- 1 | # exclude all files 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /label_buddy/projects/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/projects/__init__.py -------------------------------------------------------------------------------- /label_buddy/projects/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Relative import 4 | from .models import Project, Label 5 | 6 | 7 | # Admin now has filters and search 8 | class ProjectAmdin(admin.ModelAdmin): 9 | 10 | """ 11 | Project class for the admin site. list_display shows the fields 12 | displayed in the admin site. 13 | """ 14 | 15 | search_fields = ["title"] 16 | list_display = ["id", "title", "created_at", "users_can_see_other_queues", "project_type"] 17 | ordering = ("created_at",) 18 | list_filter = ["users_can_see_other_queues", "project_type"] 19 | 20 | 21 | class LabelAdmin(admin.ModelAdmin): 22 | 23 | """ 24 | Label class for the admin site. list_display shows the fields 25 | displayed in the admin site. 26 | """ 27 | 28 | list_display = ["name", "parent"] 29 | 30 | 31 | admin.site.register(Project, ProjectAmdin) 32 | admin.site.register(Label, LabelAdmin) 33 | -------------------------------------------------------------------------------- /label_buddy/projects/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ProjectsConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'projects' 7 | -------------------------------------------------------------------------------- /label_buddy/projects/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | from .models import Project 4 | 5 | 6 | class ProjectForm(forms.ModelForm): 7 | 8 | """ 9 | Project form for creating a project. 10 | """ 11 | 12 | title = forms.CharField(label='Tile', required=False, widget=forms.TextInput(attrs={"placeholder": "Title"})) 13 | description = forms.CharField(required=False, widget=forms.Textarea( 14 | attrs={ 15 | "placeholder": "Description", 16 | "rows": 4, 17 | } 18 | )) 19 | instructions = forms.CharField(required=False, widget=forms.Textarea( 20 | attrs={ 21 | "placeholder": "Instructions", 22 | "rows": 4, 23 | } 24 | )) 25 | new_labels = forms.CharField(label="Labels", required=False, widget=forms.Textarea( 26 | attrs={ 27 | "placeholder": "A comma separated list of new labels", 28 | "id": "new_labels", 29 | "rows": 4, 30 | } 31 | )) 32 | 33 | class Meta: 34 | model = Project 35 | fields = [ 36 | "title", 37 | "description", 38 | "instructions", 39 | "logo", 40 | "new_labels", 41 | "users_can_see_other_queues", 42 | "annotators", 43 | "reviewers", 44 | "managers", 45 | ] 46 | -------------------------------------------------------------------------------- /label_buddy/projects/migrations/.gitignore: -------------------------------------------------------------------------------- 1 | # exclude all files 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /label_buddy/projects/models.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.conf import settings 4 | from django.core.files import File 5 | from django.db import models 6 | from django.db.models.signals import pre_save, pre_delete, post_save 7 | from django.dispatch import receiver 8 | from colorfield.fields import ColorField 9 | from enumchoicefield import ChoiceEnum, EnumChoiceField 10 | 11 | from users.models import User 12 | # Create your models here 13 | 14 | 15 | class Project_type(ChoiceEnum): 16 | 17 | """ 18 | Enum class for project type (annotation of audio image or video files). 19 | """ 20 | 21 | audio = "Audio annotation" 22 | image = "Image annoation" 23 | video = "Video annotation" 24 | 25 | 26 | class Label(models.Model): 27 | 28 | """ 29 | Label class for labels created to annotate audio files. 30 | """ 31 | 32 | name = models.CharField(max_length=256, blank=False, primary_key=True, help_text='Name of the label') 33 | parent = models.ForeignKey('self', on_delete=models.CASCADE, blank=True, null=True, related_name='children', help_text='Parent label of the label (optional)') 34 | color = ColorField(blank=True, unique=True, default='#74deed', help_text='Color given to the label (optional)') 35 | 36 | # How to display labels in admin 37 | def __str__(self): 38 | return '%s' % (self.name) 39 | 40 | 41 | class Project(models.Model): 42 | 43 | """ 44 | Project class for projects created by managers (can_create_projects=True). 45 | Contains basic information about a specific project. 46 | """ 47 | 48 | title = models.CharField(max_length=256, blank=True, null=True, default='', help_text='Project title') 49 | 50 | description = models.TextField(blank=True, null=True, default='', help_text='Project description') 51 | instructions = models.TextField(blank=True, null=True, default='', help_text='Project instructions') 52 | logo = models.ImageField(upload_to='images', blank=True, help_text='Project logo') 53 | created_at = models.DateTimeField(auto_now=True, help_text='Date and time of project creation') 54 | 55 | users_can_see_other_queues = models.BooleanField(default=False, help_text='If false, annotators will see only tasks assigned to them') 56 | 57 | labels = models.ManyToManyField(Label, blank=False, help_text='Labels used by annotators to annotate') 58 | reviewers = models.ManyToManyField(User, blank=True, related_name='project_reviewer', help_text='Reviewers who will review annotations') 59 | annotators = models.ManyToManyField(User, blank=True, related_name='project_annotator', help_text='Annotators for the project') 60 | managers = models.ManyToManyField(User, blank=True, related_name='project_manager', help_text='Managers for the project') 61 | 62 | project_type = EnumChoiceField(Project_type, default=Project_type.audio, help_text='Specify the type of the annotation (Audio, image or Video)') 63 | 64 | class Meta: 65 | ordering = ['id'] 66 | 67 | # How to display projects in admin 68 | def __str__(self): 69 | return '%s' % (self.title) 70 | 71 | 72 | @receiver(pre_save, sender=Project) 73 | def auto_delete_logo_on_change(sender, instance, **kwargs): 74 | 75 | """ 76 | Deletes old logo from filesystem when corresponding project object is updated with a new file. 77 | """ 78 | 79 | pk = instance.pk 80 | if not pk: 81 | return False 82 | 83 | try: 84 | old_logo = Project.objects.get(pk=pk).logo 85 | except Project.DoesNotExist: 86 | return False 87 | 88 | if old_logo: 89 | new_logo = instance.logo 90 | if not old_logo == new_logo: 91 | if os.path.isfile(old_logo.path): 92 | os.remove(old_logo.path) 93 | 94 | 95 | @receiver(post_save, sender=Project) 96 | def set_project_title(sender, instance, created, **kwargs): 97 | 98 | """ 99 | When a project's title is no set, set a default one 100 | """ 101 | 102 | if created: 103 | title = instance.title 104 | if title: 105 | if title.strip() == "": 106 | instance.title = "Project #" + str(instance.id) 107 | else: 108 | instance.title = "Project #" + str(instance.id) 109 | instance.save() 110 | 111 | 112 | @receiver(pre_delete, sender=Project) 113 | def auto_delete_logo_on_delete(sender, instance, **kwargs): 114 | 115 | """ 116 | Delete project's logo from system after delete. 117 | """ 118 | 119 | try: 120 | project_logo = instance.logo 121 | except Project.DoesNotExist: 122 | return False 123 | 124 | if project_logo: 125 | if os.path.isfile(project_logo.path): 126 | os.remove(project_logo.path) 127 | 128 | 129 | @receiver(pre_save, sender=Project) 130 | def set_users_avatar(sender, instance, **kwargs): 131 | 132 | """ 133 | If project's logo is not specified set it to the unknown icon project. 134 | """ 135 | 136 | if not instance.logo: 137 | project_logo = open(os.path.join(settings.BASE_DIR, 'static/images/project_logo.png'), "rb") 138 | instance.logo.save('project_logo.png', File(project_logo)) 139 | -------------------------------------------------------------------------------- /label_buddy/projects/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework import permissions 2 | 3 | 4 | class UserCanCreateProject(permissions.BasePermission): 5 | 6 | """ 7 | Custom permission to only allow users who can create projects to do so. 8 | """ 9 | 10 | def has_permission(self, request, view): 11 | 12 | """ 13 | Allow to post only if user Can create projects. 14 | """ 15 | 16 | # Allow GET, HEAD or OPTIONS requests. 17 | if request.method in permissions.SAFE_METHODS: 18 | return True 19 | return request.user.can_create_projects 20 | 21 | def has_object_permission(self, request, view, obj): 22 | 23 | """ 24 | If user is part of the specific project then return true, else false. 25 | """ 26 | 27 | user = request.user 28 | if (user in obj.reviewers.all()) or (user in obj.annotators.all()) or (user in obj.managers.all()): 29 | return True 30 | 31 | return False 32 | -------------------------------------------------------------------------------- /label_buddy/projects/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .models import Project 3 | 4 | 5 | class ProjectSerializer(serializers.ModelSerializer): 6 | 7 | """ 8 | Serializer for project API endpoint data. 9 | """ 10 | 11 | class Meta: 12 | model = Project 13 | read_only_fields = [ 14 | "users_can_see_other_queues", 15 | ] 16 | fields = [ 17 | "title", 18 | "description", 19 | "instructions", 20 | "logo", 21 | "project_type", 22 | "labels", 23 | "reviewers", 24 | "annotators", 25 | "managers", 26 | ] 27 | -------------------------------------------------------------------------------- /label_buddy/projects/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from rest_framework.urlpatterns import format_suffix_patterns 3 | from django.conf.urls import url 4 | from . import views 5 | 6 | # The API URLs are now determined automatically by the router 7 | urlpatterns = [ 8 | url(r"^$", views.index, name="index_page"), 9 | url(r"^projects/create$", views.project_create_view, name="create_project"), 10 | url(r"^projects/(?P\d+)/tasks$", views.project_page_view, name="project_page"), 11 | url(r"^projects/(?P\d+)/tasks/(?P\d+)/annotation$", views.annotate_task_view, name="annotation_page"), 12 | url(r"^projects/(?P\d+)/tasks/(?P\d+)/delete$", views.task_delete_view, name="delete_task"), 13 | url(r"^projects/(?P\d+)/tasks/(?P\d+)/list_annotations$", views.list_annotations_for_task_view, name="list_task_annotations"), 14 | url(r"^projects/(?P\d+)/tasks/(?P\d+)/list_annotations/(?P\d+)/review$", views.review_annotation_view, name="review_page"), 15 | url(r"^projects/(?P\d+)/tasks/(?P\d+)/annotation/delete$", views.annotation_delete_view, name="delete_annotation"), 16 | url(r"^projects/(?P\d+)/edit$", views.project_edit_view, name="edit_project"), 17 | url(r"^projects/(?P\d+)/delete$", views.project_delete_view, name="delete_project"), 18 | # API VIEWS 19 | path('api/v1/projects/', views.ProjectList.as_view(), name="project-list"), 20 | path('api/v1/projects//', views.ProjectDetail.as_view(), name="specific_project"), 21 | path('api/v1/projects//tasks', views.ProjectTasks.as_view(), name="project-list-tasks"), 22 | path('api/v1/root', views.api_root, name="api_root"), 23 | ] 24 | 25 | urlpatterns = format_suffix_patterns(urlpatterns) 26 | -------------------------------------------------------------------------------- /label_buddy/static/.gitignore: -------------------------------------------------------------------------------- 1 | # exclude all files 2 | * 3 | # Except this file and project static files 4 | !.gitignore 5 | !.css/ 6 | !.js/ 7 | !.images/ 8 | -------------------------------------------------------------------------------- /label_buddy/static/css/annotation_page.css: -------------------------------------------------------------------------------- 1 | #waveform { 2 | /* margin-left: 1%; 3 | margin-right: 1%; */ 4 | position: relative; 5 | } 6 | 7 | #wave-timeline { 8 | margin-top: 1%; 9 | margin-left: 1%; 10 | margin-right: 1%; 11 | } 12 | 13 | #header-div { 14 | /* border: 2px solid purple; */ 15 | margin-top: 2%; 16 | text-align: right; 17 | margin-left: 1%; 18 | margin-right: 1%; 19 | } 20 | 21 | #annotation-btn-div { 22 | width: 100%; 23 | text-align: center; 24 | margin-right: 1%; 25 | margin-left: 1%; 26 | margin-top: 2%; 27 | } 28 | 29 | #commentDiv { 30 | /* border: 2px solid green; */ 31 | margin-top: 1%; 32 | margin-left: 3%; 33 | margin-right: 3%; 34 | } 35 | 36 | #commentArea { 37 | height: 80%; 38 | /* background-color: rgb(245,245,245); */ 39 | } 40 | 41 | #play-pause-btn-div { 42 | text-align: left; 43 | margin-top: .5%; 44 | } 45 | 46 | #right-main-div { 47 | /* border: solid 2px purple; */ 48 | margin-left: 1%; 49 | margin-right: 1%; 50 | } 51 | 52 | #left-main-div { 53 | border: 1px solid #007bff; 54 | background-color: rgb(245,245,245); 55 | margin-left: 1%; 56 | border-radius: 5px; 57 | } 58 | 59 | #regions-div { 60 | max-height: 280px; 61 | overflow-y: auto; 62 | -ms-overflow-style: none; 63 | scrollbar-width: none; 64 | } 65 | 66 | #regions-div::-webkit-scrollbar { 67 | display: none; 68 | } 69 | 70 | 71 | 72 | 73 | /* classes */ 74 | 75 | .my-badge { 76 | font-size: 22px; 77 | border: none; 78 | color: white; 79 | cursor: pointer; 80 | } 81 | 82 | .my-badge:focus { 83 | outline: none; 84 | box-shadow: none; 85 | } 86 | 87 | .annotation-buttons:focus { 88 | outline: none; 89 | box-shadow: none; 90 | } 91 | .annotation-buttons { 92 | width: 100px; 93 | height: 100px; 94 | } 95 | 96 | .play-pause-buttons:focus { 97 | outline: none; 98 | box-shadow: none; 99 | } 100 | 101 | /* Style the button that is used to open and close the collapsible content */ 102 | 103 | 104 | .region-buttons { 105 | margin-top: 2px; 106 | background-color: rgb(245,245,245); 107 | color: black; 108 | text-align: left; 109 | width: 100%; 110 | font-size: 15px; 111 | height: 30px; 112 | border: none; 113 | border-radius: 5px; 114 | cursor: pointer; 115 | 116 | } 117 | 118 | .region-buttons.truncate { 119 | overflow: hidden; 120 | text-overflow: ellipsis; 121 | white-space: nowrap; 122 | } 123 | 124 | .region-buttons:focus { 125 | outline:none !important; 126 | box-shadow: none; 127 | } 128 | 129 | h1,h2,h3 { 130 | vertical-align:middle; 131 | } 132 | 133 | h1>.badge, h2>.badge, h3>.badge { 134 | vertical-align:middle; 135 | 136 | } 137 | 138 | textarea:focus { 139 | border-color: rgba(116, 222, 237) !important; 140 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(116, 222, 237, .6) !important; 141 | outline: 0 none !important; 142 | } 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /label_buddy/static/css/common.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | :root { 4 | --basic_color: #007bff; 5 | } 6 | 7 | body { 8 | padding-bottom: 4%; 9 | } 10 | 11 | table 12 | { 13 | table-layout: fixed; 14 | width: 100px; 15 | } 16 | 17 | .footer-div { 18 | border-top: 1px solid white; 19 | color: #9595A6; 20 | } 21 | 22 | .myAction { 23 | /* border: 2px solid green; */ 24 | text-align: right; 25 | } 26 | 27 | .myAction:after { 28 | content:''; 29 | display:block; /* or whatever else than inline */ 30 | clear:both; 31 | } 32 | 33 | .user-form-wrap { 34 | background-color: rgb(245,245,245); 35 | padding: 3%; 36 | border-radius: 10px; 37 | margin: 2%; 38 | } 39 | 40 | #allauth-image { 41 | display:block; 42 | margin:auto; 43 | } 44 | 45 | .user-round-icon { 46 | background-color:rgb(245,245,245); 47 | } 48 | 49 | .my-button { 50 | width: 100px; 51 | background-color: var(--basic_color); 52 | } 53 | 54 | .accountHeaders { 55 | /* border: 2px solid red; */ 56 | /* position: static; */ 57 | width: 100%; 58 | 59 | font-family: Roboto; 60 | font-style: normal; 61 | font-weight: bold; 62 | font-size: 20px; 63 | align-items: center; 64 | text-align: center; 65 | 66 | /* Revised Colors/Dark Grey/650 */ 67 | 68 | color: #232337; 69 | } 70 | 71 | .forms-labels { 72 | position: static; 73 | height: 17px; 74 | 75 | font-family: Roboto; 76 | font-style: normal; 77 | font-weight: 500; 78 | font-size: 13px; 79 | line-height: 140%; 80 | /* identical to box height, or 17px */ 81 | 82 | 83 | color: #5C666B; 84 | 85 | 86 | /* Inside Auto Layout */ 87 | 88 | flex: none; 89 | order: 0; 90 | flex-grow: 0; 91 | margin: 8px 0px; 92 | } 93 | 94 | .myInput { 95 | width: 100%; 96 | display: flex; 97 | flex-direction: row; 98 | align-items: flex-start; 99 | padding: 10px; 100 | 101 | position: static; 102 | height: 42px; 103 | 104 | background: #FFFFFF; 105 | border: 1px solid #CFD8DC; 106 | box-sizing: border-box; 107 | border-radius: 4px; 108 | 109 | /* Inside Auto Layout */ 110 | 111 | flex: none; 112 | order: 0; 113 | flex-grow: 0; 114 | margin: 4px 0px; 115 | } 116 | 117 | .account-buttons { 118 | /* Auto Layout */ 119 | 120 | display: flex; 121 | flex-direction: row; 122 | justify-content: center; 123 | align-items: center; 124 | padding: 0px; 125 | 126 | position: static; 127 | width: 100%; 128 | height: 46px; 129 | border-radius: 4px; 130 | 131 | /* Inside Auto Layout */ 132 | 133 | flex: none; 134 | order: 2; 135 | flex-grow: 0; 136 | margin: 32px 0px; 137 | } 138 | 139 | .my-form-group { 140 | text-align: left; 141 | } 142 | 143 | .account-links { 144 | display: inline; 145 | font-family: Roboto; 146 | font-style: normal; 147 | font-weight: 500; 148 | font-size: 13px; 149 | text-decoration-line: underline; 150 | 151 | color: #FF85A5; 152 | } 153 | 154 | .error-text { 155 | font-family: Roboto; 156 | padding-top: 2px; 157 | font-size: 12px; 158 | font-weight: 500; 159 | color: #FF1744; 160 | } 161 | 162 | .headers-page { 163 | text-align: center; 164 | font-family: 'Roboto'; 165 | /* text-shadow: 2px 2px 2px #aaa; */ 166 | 167 | /* border: 2px solid blue; */ 168 | margin-left: 1%; 169 | margin-right: 1%; 170 | margin-top: .5%; 171 | 172 | 173 | white-space:nowrap; 174 | overflow:hidden; 175 | text-overflow:ellipsis; 176 | } 177 | 178 | .my-table-button { 179 | background-color: var(--basic_color); 180 | height: 30px; 181 | } 182 | 183 | .my-link { 184 | color: var(--basic_color); 185 | } 186 | 187 | 188 | .center-screen { 189 | display: flex; 190 | flex-direction: column; 191 | justify-content: center; 192 | align-items: center; 193 | text-align: center; 194 | min-height: 100vh; 195 | } 196 | 197 | .table-div { 198 | /* border: 2px solid lightgreen; */ 199 | margin-left: 1%; 200 | margin-right: 1%; 201 | margin-top: .5%; 202 | } 203 | 204 | .drop-down { 205 | margin-right: 8%; 206 | } 207 | 208 | .my-table-header { 209 | 210 | position: static; 211 | 212 | font-family: Roboto; 213 | font-style: normal; 214 | font-weight: normal; 215 | font-size: 15px; 216 | line-height: 16px; 217 | 218 | color: #5C666B; 219 | 220 | 221 | /* Inside Auto Layout */ 222 | 223 | flex: none; 224 | order: 0; 225 | flex-grow: 0; 226 | margin: 0px 0px; 227 | } 228 | 229 | .my-table-text { 230 | 231 | font-family: Roboto; 232 | font-style: normal; 233 | font-weight: normal; 234 | font-size: 14px; 235 | 236 | color: #1F282D; 237 | } 238 | 239 | .my-small-btn { 240 | display:block; 241 | height: 300px; 242 | width: 300px; 243 | border-radius: 50%; 244 | border: 1px solid red; 245 | 246 | } 247 | 248 | .table-hover tbody tr:hover td, .table-hover tbody tr:hover th { 249 | background-color: #E2EBF0 !important; 250 | cursor: pointer; 251 | 252 | } 253 | 254 | .page-item.active .page-link { 255 | border: none; 256 | color: black !important; 257 | background: var(--basic_color) !important; 258 | } 259 | 260 | .top-div { 261 | /* border: 2px solid lightgreen; */ 262 | margin-top: .5%; 263 | margin-left: 1%; 264 | margin-right: 1%; 265 | } 266 | 267 | .top-div:after { 268 | content:''; 269 | display:block; 270 | clear:both; 271 | } 272 | 273 | .top-icons { 274 | float: left; 275 | margin-left: 1%; 276 | margin-top: .2%; 277 | } 278 | 279 | .form-headers { 280 | font-family: 'Roboto'; 281 | /* text-shadow: 2px 2px 2px #aaa; */ 282 | 283 | white-space:nowrap; 284 | overflow:hidden; 285 | text-overflow:ellipsis; 286 | } 287 | 288 | a { 289 | text-decoration: none !important; 290 | } 291 | 292 | 293 | #nprogress .bar { 294 | background: #007bff !important; 295 | } 296 | 297 | .tooltip-icons { 298 | cursor: default; 299 | } 300 | 301 | .alert { 302 | margin: 0; 303 | } 304 | 305 | .table-striped > tbody > tr:nth-child(2n+1) > td, .table-striped > tbody > tr:nth-child(2n+1) > th { 306 | background-color: #F3F6F8; 307 | } 308 | 309 | .tooltip-body { 310 | height: 10px; 311 | overflow-y: auto; 312 | } -------------------------------------------------------------------------------- /label_buddy/static/css/delete_annotation.css: -------------------------------------------------------------------------------- 1 | .delete-header { 2 | font-family: 'Georama'; 3 | text-shadow: 2px 2px 2px #aaa; 4 | 5 | white-space:nowrap; 6 | overflow:hidden; 7 | text-overflow:ellipsis; 8 | } -------------------------------------------------------------------------------- /label_buddy/static/css/delete_task.css: -------------------------------------------------------------------------------- 1 | .delete-header { 2 | font-family: 'Georama'; 3 | text-shadow: 2px 2px 2px #aaa; 4 | 5 | white-space:nowrap; 6 | overflow:hidden; 7 | text-overflow:ellipsis; 8 | } -------------------------------------------------------------------------------- /label_buddy/static/css/index.css: -------------------------------------------------------------------------------- 1 | #edit-link { 2 | text-decoration: none !important; 3 | margin-right: 20%; 4 | color: #5cb85c; 5 | } 6 | 7 | #delete-link { 8 | margin-left: 20%; 9 | color: #d9534f; 10 | } 11 | 12 | #header-div { 13 | /* border: 2px solid purple; */ 14 | text-align: right; 15 | margin-top: 2%; 16 | margin-left: 1%; 17 | margin-right: 1%; 18 | } 19 | 20 | 21 | /* classes */ 22 | .table-title { 23 | font-size: 13px; 24 | /**Major Properties**/ 25 | white-space:nowrap; 26 | overflow:hidden; 27 | text-overflow:ellipsis; 28 | } 29 | 30 | -------------------------------------------------------------------------------- /label_buddy/static/css/list_annotations.css: -------------------------------------------------------------------------------- 1 | #header-div { 2 | /* border: 2px solid purple; */ 3 | /* text-align: left; */ 4 | margin-top: 2%; 5 | margin-left: 1%; 6 | margin-right: 1%; 7 | } 8 | 9 | #header-div:after { 10 | content:''; 11 | display:block; /* or whatever else than inline */ 12 | clear:both; 13 | } 14 | 15 | .table-title { 16 | /**Major Properties**/ 17 | white-space:nowrap; 18 | overflow:hidden; 19 | text-overflow:ellipsis; 20 | } 21 | 22 | .table-div { 23 | /* border: 2px solid lightgreen; */ 24 | margin-left: 1%; 25 | margin-right: 1%; 26 | margin-top: .5%; 27 | } -------------------------------------------------------------------------------- /label_buddy/static/css/password_reset.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-bottom: 0%; 3 | } 4 | 5 | .texts { 6 | 7 | font-family: Roboto; 8 | font-style: normal; 9 | font-weight: normal; 10 | font-size: 13px; 11 | display: inline; 12 | 13 | color: #9595A6; 14 | 15 | 16 | /* Inside Auto Layout */ 17 | } 18 | 19 | #outerDiv { 20 | background: linear-gradient(180deg, #22203D 23.73%, #29215E 97.46%); 21 | } 22 | 23 | #innerDiv { 24 | background-color: white; 25 | padding: 32px; 26 | border-radius: 4px; 27 | width: 25vw; 28 | } 29 | 30 | #underReset { 31 | margin-top: 2%; 32 | /* border: 2px solid red; */ 33 | width: 25vw; 34 | text-align: center; 35 | } -------------------------------------------------------------------------------- /label_buddy/static/css/project_page.css: -------------------------------------------------------------------------------- 1 | 2 | #header-div { 3 | margin-left: 1%; 4 | margin-right: 1%; 5 | margin-top: 2%; 6 | } 7 | 8 | #header-div:after { 9 | content:''; 10 | display:block; /* or whatever else than inline */ 11 | clear:both; 12 | } 13 | 14 | #export_div_json { 15 | background-color: rgba(0, 123, 255, 0.3); 16 | margin-top: 5%; 17 | cursor: default; 18 | } 19 | 20 | #export_approved_div { 21 | /* border: 2px solid blue; */ 22 | padding: 2%; 23 | border-radius: 5px; 24 | cursor: pointer; 25 | } 26 | 27 | #export_approved_div:hover { 28 | background-color: rgba(0, 123, 255, 0.3) !important; 29 | } 30 | 31 | #import-file { 32 | border: 1px solid #007bff; 33 | /* border-radius: 5px; */ 34 | padding: 2%; 35 | width: 60%; 36 | cursor: pointer; 37 | } 38 | 39 | #import-file:hover { 40 | background-color: rgba(0, 123, 255, 0.3) !important; 41 | } 42 | 43 | #annotate-link { 44 | text-decoration: none !important; 45 | margin-right: 20%; 46 | color: #5cb85c; 47 | } 48 | 49 | #review-link { 50 | margin-left: 20%; 51 | color: #5cb85c; 52 | } 53 | 54 | 55 | .export_div { 56 | border-radius: 5px; 57 | padding: 2%; 58 | /* border: 2px solid purple; */ 59 | margin-top: 1%; 60 | text-align: left; 61 | cursor: pointer; 62 | } 63 | 64 | .export_div:hover { 65 | background-color: rgba(0, 123, 255, 0.3) !important; 66 | } 67 | 68 | 69 | .export_name { 70 | font-weight: bold; 71 | font-size: 20px; 72 | } 73 | 74 | .export_description { 75 | color: rgba(150, 150, 150, 1); 76 | font-size: 15px; 77 | } 78 | 79 | .audio-player { 80 | width: 100%; 81 | height: 25px; 82 | outline: 0; 83 | } 84 | 85 | p { 86 | margin-left: 3%; 87 | } 88 | 89 | .table-title { 90 | /**Major Properties**/ 91 | white-space:nowrap; 92 | overflow:hidden; 93 | text-overflow:ellipsis; 94 | } 95 | 96 | /* import progress bar */ 97 | 98 | .my-progress-bar { 99 | /* display: none; */ 100 | 101 | border-radius: 5px; 102 | border: 1px solid rgb(0, 123, 255); 103 | } 104 | 105 | .progress-bar-fill { 106 | border-radius: 5px; 107 | height: 100%; 108 | width: 0%; 109 | background: rgba(0, 123, 255, 0.3); 110 | display: flex; 111 | align-items: center; 112 | transition: width 0.25s; 113 | } 114 | 115 | .progress-bar-text { 116 | margin-left: 10px; 117 | font-weight: bold; 118 | } 119 | -------------------------------------------------------------------------------- /label_buddy/static/css/review_page.css: -------------------------------------------------------------------------------- 1 | #waveform { 2 | /* margin-left: 1%; 3 | margin-right: 1%; */ 4 | position: relative; 5 | } 6 | 7 | #wave-timeline { 8 | margin-top: 1%; 9 | margin-left: 1%; 10 | margin-right: 1%; 11 | } 12 | 13 | #header-div { 14 | margin-top: 2%; 15 | text-align: right; 16 | margin-left: 1%; 17 | margin-right: 1%; 18 | } 19 | 20 | #review-date-div { 21 | margin-top: 1%; 22 | margin-left: 3%; 23 | margin-right: 3%; 24 | text-align: right; 25 | } 26 | 27 | #commentDiv { 28 | /* border: 2px solid green; */ 29 | /* margin-top: 2%; */ 30 | margin-left: 3%; 31 | margin-right: 3%; 32 | } 33 | 34 | #review-btn-div { 35 | text-align: center; 36 | margin-top: 2%; 37 | margin-left: 1%; 38 | margin-right: 1%; 39 | } 40 | 41 | #commentArea { 42 | height: 100%; 43 | } 44 | 45 | #play-pause-btn-div { 46 | text-align: left; 47 | margin-top: .5%; 48 | } 49 | 50 | #right-main-div { 51 | /* border: solid 2px purple; */ 52 | margin-left: 1%; 53 | margin-right: 1%; 54 | } 55 | 56 | #left-main-div { 57 | border: 1px solid #007bff; 58 | background-color: rgb(245,245,245); 59 | margin-left: 1%; 60 | border-radius: 5px; 61 | } 62 | 63 | #regions-div { 64 | max-height: 280px; 65 | overflow-y: auto; 66 | -ms-overflow-style: none; 67 | scrollbar-width: none; 68 | } 69 | 70 | #regions-div::-webkit-scrollbar { 71 | display: none; 72 | } 73 | 74 | /* classes */ 75 | 76 | .my-badge { 77 | font-size: 22px; 78 | border: none; 79 | color: white; 80 | cursor: pointer; 81 | } 82 | 83 | .my-badge:focus { 84 | outline: none; 85 | box-shadow: none; 86 | } 87 | 88 | .review-buttons:focus { 89 | outline: none; 90 | box-shadow: none; 91 | } 92 | .review-buttons { 93 | width: 100px; 94 | height: 100px; 95 | } 96 | 97 | .play-pause-buttons { 98 | } 99 | 100 | .play-pause-buttons:focus { 101 | outline: none; 102 | box-shadow: none; 103 | } 104 | 105 | /* Style the button that is used to open and close the collapsible content */ 106 | 107 | 108 | .region-buttons { 109 | margin-top: 2px; 110 | background-color: rgb(245,245,245); 111 | color: black; 112 | text-align: left; 113 | width: 100%; 114 | font-size: 15px; 115 | height: 30px; 116 | border: none; 117 | border-radius: 5px; 118 | cursor: pointer; 119 | 120 | } 121 | 122 | .region-buttons.truncate { 123 | overflow: hidden; 124 | text-overflow: ellipsis; 125 | white-space: nowrap; 126 | } 127 | 128 | .region-buttons:focus { 129 | outline:none !important; 130 | box-shadow: none; 131 | } 132 | 133 | h1,h2,h3 { 134 | vertical-align:middle; 135 | } 136 | 137 | h1>.badge, h2>.badge, h3>.badge { 138 | vertical-align:middle; 139 | 140 | } 141 | 142 | 143 | textarea:focus { 144 | border-color: rgba(116, 222, 237) !important; 145 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(116, 222, 237, .6) !important; 146 | outline: 0 none !important; 147 | } 148 | -------------------------------------------------------------------------------- /label_buddy/static/css/signin_page.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-bottom: 0%; 3 | } 4 | 5 | #outerDiv { 6 | background: linear-gradient(180deg, #22203D 23.73%, #29215E 97.46%); 7 | } 8 | 9 | #innerDiv { 10 | background-color: white; 11 | padding: 32px; 12 | border-radius: 4px; 13 | width: 25vw; 14 | } 15 | 16 | 17 | 18 | #id_password { 19 | border-right: 0px; 20 | } 21 | 22 | #eyeDiv { 23 | /* height: 42px; */ 24 | border-top-left-radius: 0px; 25 | border-bottom-left-radius: 0px; 26 | border-left: 0px; 27 | } 28 | 29 | #underSignUp { 30 | margin-top: 2%; 31 | /* border: 2px solid red; */ 32 | width: 25vw; 33 | text-align: center; 34 | } 35 | 36 | .links { 37 | display: inline; 38 | font-family: Roboto; 39 | font-style: normal; 40 | font-weight: 500; 41 | font-size: 13px; 42 | text-decoration-line: underline; 43 | 44 | color: #FF85A5; 45 | } 46 | 47 | .texts { 48 | 49 | font-family: Roboto; 50 | font-style: normal; 51 | font-weight: normal; 52 | font-size: 13px; 53 | display: inline; 54 | 55 | color: #9595A6; 56 | 57 | 58 | /* Inside Auto Layout */ 59 | } 60 | 61 | div { 62 | user-select: none; /* standard syntax */ 63 | -webkit-user-select: none; /* webkit (safari, chrome) browsers */ 64 | -moz-user-select: none; /* mozilla browsers */ 65 | -khtml-user-select: none; /* webkit (konqueror) browsers */ 66 | -ms-user-select: none; /* IE10+ */ 67 | } 68 | 69 | span { 70 | display: inline-block; 71 | width: 20px; 72 | } -------------------------------------------------------------------------------- /label_buddy/static/css/signup_page.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-bottom: 0%; 3 | } 4 | 5 | #outerDiv { 6 | background: linear-gradient(180deg, #22203D 23.73%, #29215E 97.46%); 7 | } 8 | 9 | #innerDiv { 10 | background-color: white; 11 | padding: 32px; 12 | border-radius: 4px; 13 | width: 25vw; 14 | } 15 | 16 | #id_password1 { 17 | border-right: 0px; 18 | } 19 | 20 | #eyeDiv { 21 | /* height: 42px; */ 22 | border-top-left-radius: 0px; 23 | border-bottom-left-radius: 0px; 24 | border-left: 0px; 25 | } 26 | 27 | .error { 28 | border: 3px solid #FF1744 !important; 29 | } 30 | 31 | .password-validation { 32 | font-family: Roboto; 33 | padding-top: 2px; 34 | font-size: 12px; 35 | font-weight: 500; 36 | color: #676767 37 | } 38 | .error-text { 39 | font-family: Roboto; 40 | padding-top: 2px; 41 | font-size: 12px; 42 | font-weight: 500; 43 | color: #FF1744; 44 | } 45 | 46 | #underSignUp { 47 | margin-top: 2%; 48 | /* border: 2px solid red; */ 49 | width: 25vw; 50 | text-align: center; 51 | } 52 | 53 | .texts { 54 | 55 | font-family: Roboto; 56 | font-style: normal; 57 | font-weight: normal; 58 | font-size: 13px; 59 | display: inline; 60 | 61 | color: #9595A6; 62 | 63 | 64 | /* Inside Auto Layout */ 65 | } 66 | 67 | div { 68 | user-select: none; /* standard syntax */ 69 | -webkit-user-select: none; /* webkit (safari, chrome) browsers */ 70 | -moz-user-select: none; /* mozilla browsers */ 71 | -khtml-user-select: none; /* webkit (konqueror) browsers */ 72 | -ms-user-select: none; /* IE10+ */ 73 | } 74 | 75 | span { 76 | display: inline-block; 77 | width: 20px; 78 | } -------------------------------------------------------------------------------- /label_buddy/static/css/welcome_page.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-bottom: 0%; 3 | } 4 | 5 | #welcomeDiv { 6 | background: linear-gradient(65.45deg, #212631 16.77%, #2F1847 79%); 7 | font-family: 'Lato'; 8 | font-style: normal; 9 | color: #FCF7F8; 10 | font-weight: 600; 11 | font-size: 28px; 12 | } 13 | 14 | #signInText { 15 | font-family: 'Lato'; 16 | /* font-style: normal; */ 17 | color: #FCF7F8; 18 | font-size: 12px; 19 | } -------------------------------------------------------------------------------- /label_buddy/static/images/favicons/Label_buddy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/static/images/favicons/Label_buddy.png -------------------------------------------------------------------------------- /label_buddy/static/images/favicons/facebook_cover_photo_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/static/images/favicons/facebook_cover_photo_1.png -------------------------------------------------------------------------------- /label_buddy/static/images/favicons/facebook_cover_photo_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/static/images/favicons/facebook_cover_photo_2.png -------------------------------------------------------------------------------- /label_buddy/static/images/favicons/facebook_profile_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/static/images/favicons/facebook_profile_image.png -------------------------------------------------------------------------------- /label_buddy/static/images/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/static/images/favicons/favicon.ico -------------------------------------------------------------------------------- /label_buddy/static/images/favicons/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/static/images/favicons/favicon.png -------------------------------------------------------------------------------- /label_buddy/static/images/favicons/linkedin_banner_image_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/static/images/favicons/linkedin_banner_image_1.png -------------------------------------------------------------------------------- /label_buddy/static/images/favicons/linkedin_banner_image_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/static/images/favicons/linkedin_banner_image_2.png -------------------------------------------------------------------------------- /label_buddy/static/images/favicons/linkedin_profile_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/static/images/favicons/linkedin_profile_image.png -------------------------------------------------------------------------------- /label_buddy/static/images/favicons/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/static/images/favicons/logo.png -------------------------------------------------------------------------------- /label_buddy/static/images/favicons/logo_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/static/images/favicons/logo_transparent.png -------------------------------------------------------------------------------- /label_buddy/static/images/favicons/logo_transparent_165x25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/static/images/favicons/logo_transparent_165x25.png -------------------------------------------------------------------------------- /label_buddy/static/images/favicons/logo_transparent_resized.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/static/images/favicons/logo_transparent_resized.png -------------------------------------------------------------------------------- /label_buddy/static/images/favicons/pinterest_board_photo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/static/images/favicons/pinterest_board_photo.png -------------------------------------------------------------------------------- /label_buddy/static/images/favicons/pinterest_profile_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/static/images/favicons/pinterest_profile_image.png -------------------------------------------------------------------------------- /label_buddy/static/images/favicons/twitter_header_photo_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/static/images/favicons/twitter_header_photo_1.png -------------------------------------------------------------------------------- /label_buddy/static/images/favicons/twitter_header_photo_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/static/images/favicons/twitter_header_photo_2.png -------------------------------------------------------------------------------- /label_buddy/static/images/favicons/twitter_profile_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/static/images/favicons/twitter_profile_image.png -------------------------------------------------------------------------------- /label_buddy/static/images/favicons/youtube_profile_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/static/images/favicons/youtube_profile_image.png -------------------------------------------------------------------------------- /label_buddy/static/images/project_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/static/images/project_logo.png -------------------------------------------------------------------------------- /label_buddy/static/images/user/user.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/static/images/user/user.jpg -------------------------------------------------------------------------------- /label_buddy/static/js/common.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | $('.clickable-row').click(function(){ 3 | window.location = $(this).attr('href'); 4 | return false; 5 | }); 6 | }); 7 | 8 | $(window).load(function(){ 9 | NProgress.done(); 10 | }); 11 | 12 | $(document).ready(function() { 13 | NProgress.start(); 14 | }); 15 | 16 | document.addEventListener('DOMContentLoaded', function() { 17 | $('.tooltip-icons').data('title'); // "This is a test"; 18 | 19 | $(function () { 20 | $('[data-toggle="tooltip"]').tooltip({ 21 | trigger : 'hover' 22 | }) 23 | }); 24 | 25 | $('form').on('submit',function(){ 26 | NProgress.start(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /label_buddy/static/js/edit_project.js: -------------------------------------------------------------------------------- 1 | // if redirect after project has no labels clear url 2 | document.addEventListener('DOMContentLoaded', function() { 3 | var url = window.location.href; 4 | parameters = url.split('?')[1] 5 | if(parameters) { 6 | document.getElementById('new_labels').style.border = '1px solid red' 7 | window.history.pushState({}, '', "/projects/" + project_id + "/edit"); 8 | } 9 | }); -------------------------------------------------------------------------------- /label_buddy/static/js/index.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | $('.clickable-row td:last-child').click(function(e){ 3 | e.stopPropagation() 4 | }); 5 | $(".clickable-row td:last-child").on({ 6 | mouseenter: function () { 7 | document.getElementById('index-table').classList.remove("table-hover"); 8 | }, 9 | mouseleave: function () { 10 | document.getElementById('index-table').classList.add("table-hover"); 11 | } 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /label_buddy/static/js/list_annotations.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | fixFilters(); 3 | }); 4 | 5 | 6 | // fix filters after page reload 7 | function fixFilters() { 8 | var parameters = window.location.href.split('?')[1]; 9 | if(parameters) { 10 | for(let param of parameters.split('&')) { 11 | name = param.split('=')[0]; 12 | value = param.split('=')[1].toLowerCase(); 13 | 14 | if(name == 'approved') { 15 | if(value == 'true') { 16 | document.getElementById("approved").checked = true; 17 | } 18 | } else if(name == 'rejected') { 19 | if(value == 'true') { 20 | document.getElementById("rejected").checked = true; 21 | } 22 | } else if(name == 'unreviewed') { 23 | if(value == 'true') { 24 | document.getElementById("unreviewed").checked = true; 25 | } 26 | } 27 | } 28 | } 29 | 30 | } 31 | 32 | // filter annotations on list_annotations page 33 | function filter_tasks() { 34 | var url = window.location.href; 35 | url = url.split('?')[0] 36 | var approved = document.getElementById("approved").checked; 37 | var rejected = document.getElementById("rejected").checked; 38 | var unreviewed = document.getElementById("unreviewed").checked; 39 | 40 | 41 | // check if all are checked then uncheck them 42 | if(approved && rejected && unreviewed) { 43 | // select all annotations 44 | // just reload 45 | } else { 46 | // url here has no parameters 47 | if(approved) 48 | url += '?approved=true'; 49 | 50 | if(rejected) { 51 | if (url.indexOf('?') > -1) { 52 | url += '&rejected=true' 53 | } else{ 54 | url += '?rejected=true' 55 | } 56 | } 57 | 58 | if(unreviewed) { 59 | if (url.indexOf('?') > -1) { 60 | url += '&unreviewed=true' 61 | } else{ 62 | url += '?unreviewed=true' 63 | } 64 | } 65 | } 66 | window.location.href = url; 67 | } -------------------------------------------------------------------------------- /label_buddy/static/js/signin_page.js: -------------------------------------------------------------------------------- 1 | const password = document.querySelector('#id_password'); 2 | document.addEventListener('DOMContentLoaded', function() { 3 | $('#eyeDiv').click(function() { 4 | // toggle the type attribute 5 | const type = password.getAttribute('type') === 'password' ? 'text' : 'password'; 6 | if(password.getAttribute('type') === 'password') { 7 | document.getElementById("togglePassword").classList.toggle('fa-eye-slash'); 8 | } else { 9 | document.getElementById("togglePassword").classList.toggle('fa-eye'); 10 | } 11 | password.setAttribute('type', type); 12 | // toggle the eye slash icon 13 | 14 | }); 15 | }); -------------------------------------------------------------------------------- /label_buddy/static/js/signup_page.js: -------------------------------------------------------------------------------- 1 | const password = document.querySelector('#id_password1'); 2 | document.addEventListener('DOMContentLoaded', function() { 3 | $('#eyeDiv').click(function() { 4 | // toggle the type attribute 5 | const type = password.getAttribute('type') === 'password' ? 'text' : 'password'; 6 | if(password.getAttribute('type') === 'password') { 7 | document.getElementById("togglePassword").classList.toggle('fa-eye-slash'); 8 | } else { 9 | document.getElementById("togglePassword").classList.toggle('fa-eye'); 10 | } 11 | password.setAttribute('type', type); 12 | // toggle the eye slash icon 13 | 14 | }); 15 | }); -------------------------------------------------------------------------------- /label_buddy/static/js/welcome_page.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', function() { 2 | // Your application has indicated there's an error 3 | window.setTimeout(function(){ 4 | 5 | // Move to a new location or you can do something else 6 | window.location.href = "/accounts/login"; 7 | 8 | }, 3000); 9 | }); -------------------------------------------------------------------------------- /label_buddy/tasks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/tasks/__init__.py -------------------------------------------------------------------------------- /label_buddy/tasks/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Relative import 4 | from .models import ( 5 | Task, 6 | Comment, 7 | Annotation 8 | ) 9 | 10 | 11 | # Admin now has filters and search 12 | class TaskAdmin(admin.ModelAdmin): 13 | 14 | """ 15 | Task class for the admin site. list_display shows the fields 16 | displayed in the admin site. 17 | """ 18 | 19 | search_fields = ["project"] 20 | list_display = ["id", "project", "file", "status", "review_status"] 21 | ordering = ("id",) 22 | list_filter = ["status", "review_status"] 23 | 24 | 25 | class CommentAdmin(admin.ModelAdmin): 26 | 27 | """ 28 | Comment class for the admin site. list_display shows the fields 29 | displayed in the admin site. 30 | """ 31 | 32 | search_fields = ["reviewed_by"] 33 | list_display = ["id", "reviewed_by", "annotation", "created_at"] 34 | ordering = ("id",) 35 | 36 | 37 | class AnnotationAdmin(admin.ModelAdmin): 38 | 39 | """ 40 | Annotation class for the admin site. list_display shows the fields 41 | displayed in the admin site. 42 | """ 43 | 44 | search_fields = ["user", "project"] 45 | list_display = ["id", "task", "project", "user", "created_at", "rejected_by_user", "hidden_by_user", "review_status"] 46 | ordering = ("id",) 47 | list_filter = ["rejected_by_user", "hidden_by_user"] 48 | 49 | 50 | admin.site.register(Task, TaskAdmin) 51 | admin.site.register(Comment, CommentAdmin) 52 | admin.site.register(Annotation, AnnotationAdmin) 53 | -------------------------------------------------------------------------------- /label_buddy/tasks/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class TasksConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'tasks' 7 | -------------------------------------------------------------------------------- /label_buddy/tasks/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from .models import Task 3 | 4 | 5 | class TaskForm(forms.ModelForm): 6 | 7 | """ 8 | Task form for uploading a file in the project page. 9 | """ 10 | 11 | file = forms.FileField(label='', widget=forms.FileInput(attrs={"id": "import-file", "accept": ".wav, .mp3, .mp4, .zip"})) 12 | 13 | class Meta: 14 | model = Task 15 | fields = [ 16 | "file", 17 | ] 18 | -------------------------------------------------------------------------------- /label_buddy/tasks/helpers.py: -------------------------------------------------------------------------------- 1 | from users.models import User 2 | from .models import ( 3 | Annotation, 4 | Task, 5 | Status, 6 | Comment, 7 | Annotation_status, 8 | ) 9 | 10 | 11 | def get_user(username): 12 | 13 | """ 14 | Get user by username. 15 | """ 16 | 17 | try: 18 | user = User.objects.get(username=username) 19 | return user 20 | except User.DoesNotExist: 21 | return None 22 | 23 | 24 | def get_annotation(task, project, user): 25 | 26 | """ 27 | Get annotation by task, project and user. 28 | """ 29 | 30 | try: 31 | annotation = Annotation.objects.get(task=task, project=project, user=user) 32 | return annotation 33 | except Annotation.DoesNotExist: 34 | return None 35 | 36 | 37 | def get_review(annotation): 38 | 39 | """ 40 | Get annotation's review (if exists). 41 | """ 42 | 43 | try: 44 | review = Comment.objects.get(annotation=annotation) 45 | return review 46 | except Comment.DoesNotExist: 47 | return None 48 | 49 | 50 | def export_data(project, export_only_approved): 51 | 52 | """ 53 | For all tasks of project which have been annotated result will be an array of dicts. Each dict will represent a task 54 | which will contain all annotation completed for this task. 55 | """ 56 | 57 | exported_result = [] 58 | skipped_annotations = 0 59 | # Get all annotated tasks of project 60 | annotated_tasks = Task.objects.filter(project=project, status=Status.labeled) 61 | 62 | for task in annotated_tasks: 63 | task_dict = { 64 | "id": task.id, 65 | "annotations": [], 66 | "file_upload": task.original_file_name, 67 | "data": { 68 | "audio": task.file.url, 69 | }, 70 | "project_created_at": project.created_at.strftime("%Y-%m-%d %H:%M:%S"), 71 | "project": project.id, 72 | } 73 | 74 | task_annotations = Annotation.objects.filter(task=task, project=project) 75 | 76 | # For every annotation, push it 77 | for annotation in task_annotations: 78 | if export_only_approved: 79 | if annotation.review_status == Annotation_status.approved: 80 | annotation_user = annotation.user 81 | review = get_review(annotation) 82 | assert review is not None, "Annotation approved but not reviewed" 83 | annotation_dict = { 84 | "id": annotation.id, 85 | "completed_by": { 86 | "id": annotation_user.id, 87 | "username": annotation_user.username, 88 | "email": annotation_user.email, 89 | "name": annotation_user.name, 90 | }, 91 | "reviewed_by": {} if not review else { 92 | "id": review.reviewed_by.id, 93 | "username": review.reviewed_by.username, 94 | "email": review.reviewed_by.email, 95 | "name": review.reviewed_by.name, 96 | "review_status": annotation.review_status.name, 97 | "review_created_at": review.created_at.strftime("%Y-%m-%d %H:%M:%S"), 98 | "review_updated_at": review.updated_at.strftime("%Y-%m-%d %H:%M:%S") if review.updated_at else "", 99 | }, 100 | "result": annotation.result, 101 | "created_at": annotation.created_at.strftime("%Y-%m-%d %H:%M:%S"), 102 | "updated_at": annotation.updated_at.strftime("%Y-%m-%d %H:%M:%S") if annotation.updated_at else "", 103 | "task": task.id, 104 | } 105 | task_dict["annotations"].append(annotation_dict) 106 | else: 107 | skipped_annotations += 1 108 | else: 109 | annotation_user = annotation.user 110 | review = get_review(annotation) 111 | annotation_dict = { 112 | "id": annotation.id, 113 | "completed_by": { 114 | "id": annotation_user.id, 115 | "username": annotation_user.username, 116 | "email": annotation_user.email, 117 | "name": annotation_user.name, 118 | }, 119 | "reviewed_by": {} if not review else { 120 | "id": review.reviewed_by.id, 121 | "username": review.reviewed_by.username, 122 | "email": review.reviewed_by.email, 123 | "name": review.reviewed_by.name, 124 | "review_status": annotation.review_status.name, 125 | "review_created_at": review.created_at.strftime("%Y-%m-%d %H:%M:%S"), 126 | "review_updated_at": review.updated_at.strftime("%Y-%m-%d %H:%M:%S") if review.updated_at else "", 127 | }, 128 | "result": annotation.result, 129 | "created_at": annotation.created_at.strftime("%Y-%m-%d %H:%M:%S"), 130 | "updated_at": annotation.updated_at.strftime("%Y-%m-%d %H:%M:%S") if annotation.updated_at else "", 131 | "task": task.id, 132 | } 133 | task_dict["annotations"].append(annotation_dict) 134 | 135 | exported_result.append(task_dict) 136 | return exported_result, skipped_annotations 137 | -------------------------------------------------------------------------------- /label_buddy/tasks/migrations/.gitignore: -------------------------------------------------------------------------------- 1 | # exclude all files 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /label_buddy/tasks/models.py: -------------------------------------------------------------------------------- 1 | import os 2 | import jsonfield 3 | 4 | from django.db import models 5 | from django.core.exceptions import ValidationError 6 | from django.db.models.signals import post_save, pre_save, pre_delete, post_delete 7 | from django.dispatch import receiver 8 | from enumchoicefield import ChoiceEnum, EnumChoiceField 9 | from url_or_relative_url_field.fields import URLOrRelativeURLField 10 | 11 | from users.models import User 12 | from projects.models import Project 13 | 14 | 15 | def get_review(annotation): 16 | 17 | """ 18 | Get a review done for an annotation (if exists). 19 | """ 20 | 21 | try: 22 | review = Comment.objects.get(annotation=annotation) 23 | return review 24 | except Comment.DoesNotExist: 25 | return None 26 | 27 | 28 | # Create your models here 29 | class Status(ChoiceEnum): 30 | 31 | """ 32 | Enum class for task status. 33 | """ 34 | 35 | labeled = "Labeled" 36 | unlabeled = "Unlabeled" 37 | 38 | 39 | class Review_status(ChoiceEnum): 40 | 41 | """ 42 | Enum class for task review_status. 43 | """ 44 | 45 | unreviewed = "Unreviewed" 46 | reviewed = "Reviewed" 47 | 48 | 49 | class Annotation_status(ChoiceEnum): 50 | 51 | """ 52 | Review status for each annotation. 53 | """ 54 | 55 | approved = "Approved" 56 | rejected = "Rejected" 57 | no_review = "Unreviewed" 58 | 59 | 60 | class Task(models.Model): 61 | 62 | """ 63 | Task class to store audio (image or video) files for each project. 64 | Tasks will be completed (annotated) by annotatos. 65 | """ 66 | 67 | project = models.ForeignKey(Project, on_delete=models.CASCADE, help_text='Project to which the task belongs') 68 | file = models.FileField(upload_to='audio', blank=True, null=True, help_text='Local file uploaded') 69 | original_file_name = models.CharField(max_length=256, blank=True, null=True, default='', help_text='Task file original file name') 70 | url = URLOrRelativeURLField(blank=True, help_text='URL for a file') 71 | 72 | extra = jsonfield.JSONField(blank=True, null=True, default=None, help_text='Extra info about the task') 73 | status = EnumChoiceField(Status, default=Status.unlabeled, help_text='If the task is annotated status must be labeled else unlabeled') 74 | review_status = EnumChoiceField(Review_status, default=Review_status.unreviewed, help_text='Status for reviews') 75 | 76 | assigned_to = models.ManyToManyField(User, blank=True, related_name='task_annotators', help_text='Annotators who will annotate the task') 77 | 78 | class Meta: 79 | ordering = ['-id'] 80 | 81 | # We ensure that even one of file or url should have a value 82 | def clean(self): 83 | if not self.file and not self.url: # This will check for None or Empty 84 | raise ValidationError({'file': 'Even one of file or url should have a value.'}) 85 | 86 | def __str__(self): 87 | return 'Task: %d - project: %s' % (self.id, self.project) 88 | 89 | 90 | class Annotation(models.Model): 91 | 92 | """ 93 | Annotation class for annotations done by annotators. 94 | Annotation format will be in JSON format. 95 | """ 96 | 97 | task = models.ForeignKey(Task, on_delete=models.CASCADE, blank=False, help_text='Task to which the annotation belongs') 98 | project = models.ForeignKey(Project, on_delete=models.CASCADE, blank=False, help_text='Project to which the annotation belongs') 99 | user = models.ForeignKey(User, on_delete=models.CASCADE, blank=False, related_name='annotation_user', help_text='User who made the annotation') 100 | 101 | created_at = models.DateTimeField(auto_now=True, help_text='Date and time of annotation creation') 102 | updated_at = models.DateTimeField(blank=True, null=True, help_text='Date and time of update') 103 | 104 | result = jsonfield.JSONField(blank=True, null=True, help_text='The result of the annotation in JSON format') 105 | 106 | reviewed_at = models.DateTimeField(blank=True, null=True, help_text='Date and time of a review') 107 | 108 | rejected_by_user = models.BooleanField(default=False, help_text='Annotation rejected by user true/false') 109 | hidden_by_user = models.BooleanField(default=False, help_text='Hidden by users true/false') 110 | 111 | review_status = EnumChoiceField(Annotation_status, default=Annotation_status.no_review, help_text='Status for annotation review') 112 | 113 | class Meta: 114 | unique_together = ('user', 'task',) 115 | 116 | def __str__(self): 117 | return 'Annotation %d - project: %s' % (self.id, self.project) 118 | 119 | 120 | class Comment(models.Model): 121 | 122 | """ 123 | Comment class for comments done by reviewers. 124 | Annotators will be able to see the comments on their annotations. 125 | """ 126 | 127 | reviewed_by = models.ForeignKey(User, on_delete=models.CASCADE, blank=False, related_name='annotation_reviewer', help_text='Reviewer who made the review') 128 | annotation = models.ForeignKey(Annotation, on_delete=models.CASCADE, blank=False, related_name='annotation_reviewed', help_text='Annotation reviewed') 129 | 130 | comment = models.TextField(blank=False, help_text='Comment for an annotation') 131 | created_at = models.DateTimeField(auto_now=True, help_text='Date and time of comment creation') 132 | updated_at = models.DateTimeField(blank=True, null=True, help_text='Date and time of update') 133 | 134 | class Meta: 135 | unique_together = ('reviewed_by', 'annotation',) 136 | 137 | def __str__(self): 138 | return 'Comment from %s' % (self.reviewed_by) 139 | 140 | 141 | # SIGNALS 142 | @receiver(post_save, sender=Annotation) 143 | def make_task_labeled(sender, instance, created, **kwargs): 144 | 145 | """ 146 | When an annotation is saved, mark the corresponding task as labeled (if it's the first annotation for the task). 147 | """ 148 | 149 | task = instance.task 150 | if created and task.status == Status.unlabeled: 151 | task = instance.task 152 | task.status = Status.labeled 153 | task.save() 154 | 155 | 156 | @receiver(post_save, sender=Comment) 157 | def check_if_task_reviewed(sender, instance, created, **kwargs): 158 | 159 | """ 160 | If all annotations which belong to the task are reviewed, then make task reviewed. 161 | """ 162 | 163 | task = instance.annotation.task 164 | all_annotations = Annotation.objects.filter(task=task, project=task.project) 165 | task_reviewed = True # If passes all validations it will be reviewed 166 | for annotation in all_annotations: 167 | if not get_review(annotation): 168 | task_reviewed = False 169 | break 170 | if task_reviewed: 171 | task.review_status = Review_status.reviewed 172 | task.save() 173 | 174 | 175 | @receiver(pre_save, sender=Annotation) 176 | def make_annotation_unreviewed_pre_save(sender, instance, **kwargs): 177 | 178 | """ 179 | If annotation result updated, make status unreviewed so reviewer can review the new annotaion. 180 | """ 181 | 182 | try: 183 | annotation = Annotation.objects.get(pk=instance.pk) 184 | except Annotation.DoesNotExist: 185 | return False 186 | 187 | if not (instance.result == annotation.result): 188 | instance.review_status = Annotation_status.no_review 189 | 190 | 191 | @receiver(pre_delete, sender=Comment) 192 | def make_annotation_unreviewed_pre_delete(sender, instance, **kwargs): 193 | 194 | """ 195 | When a comment is deleted, set annotations status to no_review. 196 | """ 197 | 198 | try: 199 | annotation = instance.annotation 200 | except Annotation.DoesNotExist: 201 | return False 202 | 203 | annotation.review_status = Annotation_status.no_review 204 | annotation.save() 205 | 206 | 207 | @receiver(post_delete, sender=Comment) 208 | def make_annotation_unreviewed_post_delete(sender, instance, **kwargs): 209 | 210 | """ 211 | If task's annotations are not reviewed make it unreviewed. 212 | """ 213 | 214 | try: 215 | annotation = instance.annotation 216 | except Annotation.DoesNotExist: 217 | return False 218 | 219 | task = instance.annotation.task 220 | all_annotations = Annotation.objects.filter(task=task, project=task.project) 221 | task_reviewed = True # If passes all validations it will be reviewed 222 | for annotation in all_annotations: 223 | if not get_review(annotation): 224 | task_reviewed = False 225 | break 226 | if not task_reviewed: 227 | task.review_status = Review_status.unreviewed 228 | task.save() 229 | 230 | 231 | @receiver(pre_delete, sender=Annotation) 232 | def mark_task_unlabeled(sender, instance, **kwargs): 233 | 234 | """ 235 | After deleting an annotation check task and if it has no other annotations, mark it as unlabeled. 236 | """ 237 | 238 | try: 239 | task_annotation = instance.task 240 | except Annotation.DoesNotExist: 241 | return False 242 | 243 | task_annotations = Annotation.objects.filter(task=task_annotation).count() - 1 244 | if task_annotations == 0: 245 | task_annotation.status = Status.unlabeled 246 | task_annotation.save() 247 | 248 | 249 | @receiver(pre_delete, sender=Task) 250 | def auto_delete_files(sender, instance, **kwargs): 251 | 252 | """ 253 | Delete task's file from system after delete. 254 | """ 255 | 256 | try: 257 | task_file = instance.file 258 | except Task.DoesNotExist: 259 | return False 260 | 261 | if task_file: 262 | if os.path.isfile(task_file.path): 263 | os.remove(task_file.path) 264 | -------------------------------------------------------------------------------- /label_buddy/tasks/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .models import Task 3 | 4 | 5 | class TaskSerializer(serializers.ModelSerializer): 6 | 7 | """ 8 | Serializer for task API endpoint data. 9 | """ 10 | 11 | class Meta: 12 | model = Task 13 | fields = [ 14 | "project", 15 | "file", 16 | "url", 17 | "extra", 18 | "status", 19 | "review_status", 20 | "assigned_to", 21 | ] 22 | -------------------------------------------------------------------------------- /label_buddy/tasks/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from rest_framework.urlpatterns import format_suffix_patterns 3 | from django.conf.urls import url 4 | from . import views 5 | 6 | 7 | # The API URLs are now determined automatically by the router 8 | urlpatterns = [ 9 | 10 | # API VIEWS 11 | path('api/v1/tasks/', views.TaskList.as_view(), name="task-list"), 12 | url(r"^api/v1/projects/(?P\d+)/tasks/(?P\d+)/annotation/save$", views.AnnotationSave.as_view(), name="save_annotation"), 13 | url(r"^api/v1/projects/(?P\d+)/tasks/export$", views.ExportData.as_view(), name="export_data"), 14 | ] 15 | 16 | urlpatterns = format_suffix_patterns(urlpatterns) 17 | -------------------------------------------------------------------------------- /label_buddy/tasks/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | from django.utils import timezone 3 | from django.contrib import messages 4 | 5 | from rest_framework.response import Response 6 | from rest_framework.views import APIView 7 | from rest_framework.exceptions import PermissionDenied 8 | from rest_framework import ( 9 | permissions, 10 | status, 11 | ) 12 | 13 | from projects.models import Project 14 | from .models import Task, Annotation 15 | from .serializers import TaskSerializer 16 | from .helpers import ( 17 | get_user, 18 | get_annotation, 19 | export_data, 20 | ) 21 | # Create your views here 22 | 23 | 24 | # API VIEWS 25 | class TaskList(APIView): 26 | 27 | permission_classes = (permissions.IsAuthenticatedOrReadOnly,) 28 | serializer_class = TaskSerializer 29 | 30 | """ 31 | List all tasks or create a new one. 32 | """ 33 | 34 | # Get request 35 | def get(self, request, format=None): 36 | 37 | tasks = Task.objects.all() 38 | serializer = TaskSerializer(tasks, many=True) 39 | 40 | return Response(serializer.data) 41 | 42 | # Post request 43 | def post(self, request, format=None): 44 | 45 | serializer = TaskSerializer(data=request.data) 46 | 47 | if serializer.is_valid(): 48 | serializer.save() 49 | return Response(serializer.data, status=status.HTTP_201_CREATED) 50 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 51 | 52 | 53 | class AnnotationSave(APIView): 54 | 55 | """ 56 | Only post is implemented here. 57 | """ 58 | 59 | def get_project(self, pk): 60 | try: 61 | return Project.objects.get(pk=pk) 62 | except PermissionDenied: 63 | return Response({"message": "No permissions"}, status=status.HTTP_401_UNAUTHORIZED) 64 | except Project.DoesNotExist: 65 | return Response({"message": "Project does not exist"}, status=status.HTTP_400_BAD_REQUEST) 66 | 67 | def get_task(self, task_pk): 68 | try: 69 | return Task.objects.get(pk=task_pk) 70 | except PermissionDenied: 71 | return Response({"message": "No permissions"}, status=status.HTTP_401_UNAUTHORIZED) 72 | except Task.DoesNotExist: 73 | return Response({"message": "Task does not exist"}, status=status.HTTP_400_BAD_REQUEST) 74 | 75 | def post(self, request, pk, task_pk, format=None): 76 | 77 | # Checks for user 78 | user = get_user(request.user.username) 79 | if not user or (user != request.user): 80 | return Response({"message": "Something is wrong with the user!"}, status=status.HTTP_400_BAD_REQUEST) 81 | 82 | # Check if project exists 83 | project = self.get_project(pk) 84 | if isinstance(project, HttpResponse): 85 | return Response(project.data, status=project.status_code) 86 | 87 | # Check if task exists 88 | task = self.get_task(task_pk) 89 | if isinstance(task, HttpResponse): 90 | return Response(task.data, status=task.status_code) 91 | 92 | # Check if user is annotator for this project 93 | if user not in project.annotators.all(): 94 | return Response({"message": "You are not an annotator for this project!"}, status=status.HTTP_400_BAD_REQUEST) 95 | 96 | # Check if task belongs to project 97 | if task.project != project: 98 | message = "Task " + str(task.id) + " does not belong to project " + project.title + "!" 99 | return Response({"message": message}, status=status.HTTP_400_BAD_REQUEST) 100 | 101 | # If all validations pass, save annotations 102 | # Check if an annotation already exists. If not create one, else change result and update date 103 | annotation = get_annotation(task, project, user) 104 | result = request.data 105 | 106 | # If annotation is not empty 107 | if annotation: 108 | if result != []: 109 | # Update existing annotation 110 | annotation.result = result 111 | annotation.updated_at = timezone.now() 112 | annotation.save() 113 | else: 114 | annotation.delete() 115 | messages.add_message(request, messages.SUCCESS, "Annotation deleted.") 116 | return Response({}, status=status.HTTP_200_OK) 117 | else: 118 | if result != []: 119 | # Create new annotation 120 | Annotation.objects.create( 121 | task=task, 122 | project=project, 123 | user=user, 124 | result=result, 125 | ) 126 | else: 127 | messages.add_message(request, messages.ERROR, "You submitted an empty annotation.") 128 | return Response({}, status=status.HTTP_400_BAD_REQUEST) 129 | messages.add_message(request, messages.SUCCESS, "Annotation saved successfully.") 130 | return Response({}, status=status.HTTP_200_OK) 131 | 132 | 133 | class ExportData(APIView): 134 | 135 | """ 136 | API endpoint for exporting data for a project. 137 | Only post is implemented here. 138 | """ 139 | 140 | def get_project(self, pk): 141 | try: 142 | return Project.objects.get(pk=pk) 143 | except PermissionDenied: 144 | return Response({"message": "No permissions"}, status=status.HTTP_401_UNAUTHORIZED) 145 | except Project.DoesNotExist: 146 | return Response({"message": "Project does not exist"}, status=status.HTTP_400_BAD_REQUEST) 147 | 148 | def post(self, request, pk, format=None): 149 | 150 | # Checks for user 151 | user = get_user(request.user.username) 152 | if not user or (user != request.user): 153 | return Response({"message": "Something is wrong with the user!"}, status=status.HTTP_400_BAD_REQUEST) 154 | 155 | # Check if project exists 156 | project = self.get_project(pk) 157 | if isinstance(project, HttpResponse): 158 | return Response(project.data, status=project.status_code) 159 | 160 | # Check if user is part of this project 161 | if (user not in project.reviewers.all()) and (user not in project.annotators.all()) and (user not in project.managers.all()): 162 | return Response({"message": "You are not involved to this project!"}, status=status.HTTP_400_BAD_REQUEST) 163 | 164 | # If all validations pass, return exported json 165 | exported_json, skipped_annotations = export_data(project, request.data['exportApproved']) 166 | 167 | # Create filename 168 | if request.data['exportApproved']: 169 | exported_name = "project-" + str(project.id) + "-ONLY_APPROVED-export_at-" + project.created_at.strftime("%Y-%m-%d-%H:%M:%S") 170 | else: 171 | exported_name = "project-" + str(project.id) + "-export_at-" + project.created_at.strftime("%Y-%m-%d-%H:%M:%S") 172 | 173 | if request.data['exportApproved']: 174 | message = str(skipped_annotations) + " unapproved annotations were skipped. Succesfully exported " + exported_name + "." 175 | else: 176 | message = "Succesfully exported " + exported_name + "." 177 | exported_name += ".json" if request.data['format'] == "JSON" else ".csv" 178 | data = { 179 | "format": request.data['format'], 180 | "exported_json": exported_json, 181 | "exported_name": exported_name, 182 | "message": message 183 | } 184 | return Response(data, status=status.HTTP_200_OK) 185 | -------------------------------------------------------------------------------- /label_buddy/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% load static %} 4 | 5 | {% block head_files %} 6 | {{ block.super }} 7 | 8 | {% endblock %} 9 | 10 | {% block title %} ◦ Welcome {% endblock %} 11 | 12 | {% block navbar %}{% endblock %} 13 | {% block content %} 14 |
15 | Logo 16 | YOU ARE LOST! 17 |
18 | {% endblock %} 19 | {% block footer %}{% endblock %} -------------------------------------------------------------------------------- /label_buddy/templates/account/account_inactive.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block title %} ◦ Account Inactive {% endblock %} 6 | 7 | {% block content %} 8 | {{ block.super }} 9 |

{% trans "Account Inactive" %}

10 | 11 |

{% trans "This account is inactive." %}

12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /label_buddy/templates/account/email.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load i18n %} 4 | {% comment %} 5 | {% block title %} ◦ Account {% endblock %} 6 | 7 | {% block content %} 8 |
9 | 10 | {% block messages %} 11 | {% if messages %} 12 | {% for message in messages %} 13 | 19 | {% endfor %} 20 | {% endif %} 21 | {% endblock %} 22 | 23 |

{% trans "E-mail Addresses" %}

24 | 25 | {% if user.emailaddress_set.all %} 26 | 27 | 28 | 29 |
30 | {% csrf_token %} 31 | 32 | {% for emailaddress in user.emailaddress_set.all %} 33 |
34 | 35 | 52 | 53 |
54 | 55 | {% endfor %} 56 | 57 |
58 | 59 | 60 | 61 |
62 | 63 | 64 |
65 | 66 | {% else %} 67 | 68 | 69 | 70 | {% endif %} 71 | 72 | 73 |

{% trans "Add E-mail Address" %}

74 | 75 |
76 | {% csrf_token %} 77 | {{ form.as_p }} 78 | 79 |
80 | 81 | {% endblock %} 82 | 83 |
84 | 85 | {% block extra_body %} 86 | 99 | {% endblock %} 100 | {% endcomment %} 101 | -------------------------------------------------------------------------------- /label_buddy/templates/account/email/email_confirmation_message.txt: -------------------------------------------------------------------------------- 1 | {% load account %}{% user_display user as user_display %}{% load i18n %}{% autoescape off %}{% blocktrans with site_name=current_site.name site_domain=current_site.domain %}Hello from {{ site_name }}! 2 | 3 | User {{ user_display }} has given your as an e-mail address to connect their account. 4 | 5 | To confirm this is correct, go to {{ activate_url }} 6 | {% endblocktrans %}{% endautoescape %} 7 | {% blocktrans with site_name=current_site.name site_domain=current_site.domain %}Thank you from {{ site_name }}! 8 | {{ site_domain }}{% endblocktrans %} -------------------------------------------------------------------------------- /label_buddy/templates/account/email/email_confirmation_signup_message.txt: -------------------------------------------------------------------------------- 1 | {% include "account/email/email_confirmation_message.txt" %} 2 | -------------------------------------------------------------------------------- /label_buddy/templates/account/email/email_confirmation_signup_subject.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% autoescape off %} 3 | {% blocktrans %}Confirm Your E-mail{% endblocktrans %} 4 | {% endautoescape %} 5 | -------------------------------------------------------------------------------- /label_buddy/templates/account/email/email_confirmation_subject.txt: -------------------------------------------------------------------------------- 1 | Confirm Your Email. -------------------------------------------------------------------------------- /label_buddy/templates/account/email/password_reset_key_message.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %}{% blocktrans with site_name=current_site.name site_domain=current_site.domain %}Hello from {{ site_name }}! 2 | 3 | You requested a password reset for your user account. 4 | Click the link below to reset your password.{% endblocktrans %} 5 | 6 | {{ password_reset_url }} 7 | 8 | {% if username %}{% blocktrans %}In case you forgot, your username is {{ username }}.{% endblocktrans %} 9 | {% endif %} -------------------------------------------------------------------------------- /label_buddy/templates/account/email/password_reset_key_subject.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% autoescape off %} 3 | {% blocktrans %}Password Reset E-mail{% endblocktrans %} 4 | {% endautoescape %} 5 | -------------------------------------------------------------------------------- /label_buddy/templates/account/email_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% load account %} 6 | 7 | {% block title %} ◦ Confirm E-mail Address {% endblock %} 8 | 9 | 10 | {% block content %} 11 | {{ block.super }} 12 |
13 |
14 |
15 |

{% trans "Confirm E-mail Address" %}

16 |
17 | {% if confirmation %} 18 | 19 | {% user_display confirmation.email_address.user as user_display %} 20 | 21 |

{% blocktrans with confirmation.email_address.email as email %} Please confirm that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktrans %}

22 | 23 |
24 | {% csrf_token %} 25 | 26 |
27 | 28 | {% else %} 29 | 30 | {% url 'account_email' as email_url %} 31 | 32 |

{% blocktrans %}This e-mail confirmation link expired or is invalid. Please issue a new e-mail confirmation request. You will be redirected to login page in 5 seconds.{% endblocktrans %}

33 | 36 | 37 | {% endif %} 38 |
39 |
40 |
41 | {% endblock %} 42 | -------------------------------------------------------------------------------- /label_buddy/templates/account/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% load static %} 4 | 5 | {% block head_files %} 6 | {{ block.super }} 7 | 8 | 9 | {% endblock %} 10 | 11 | {% block title %} ◦ Sign in {% endblock %} 12 | 13 | {% block navbar %}{% endblock %} 14 | 15 | {% block content %} 16 |
17 | Logo 18 |
19 |

Sign in

20 | {% for message in messages %} 21 | {% if message.level == DEFAULT_MESSAGE_LEVELS.SUCCESS %} 22 |
23 | {{ message|escape }} 24 |
25 | {% endif %} 26 | {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %} 27 |
28 | {{ message|escape }} 29 |
30 | {% endif %} 31 | {% endfor %} 32 | {% if form.errors %} 33 | {% for field in form %} 34 | {% for error in field.errors %} 35 |
36 | {{ error|escape }} 37 |
38 | {% endfor %} 39 | {% endfor %} 40 | {% for error in form.non_field_errors %} 41 |
42 | {{ error|escape }} 43 |
44 | {% endfor %} 45 | {% endif %} 46 |
47 | {% csrf_token %} 48 | {% for field in form %} 49 | 50 | {% if field.name == 'remember'%} 51 |
52 | {% else %} 53 |
54 | {% endif %} 55 | 56 | {% if field.name == "password" %} 57 |
58 | {{ field }} 59 |
60 |
61 | 62 | 63 | 64 |
65 |
66 |
67 | {% else %} 68 | {{ field }} 69 | {% endif %} 70 |
71 | {% endfor %} 72 | 73 | 74 |
75 |
76 | 77 | 80 | 81 |

82 | • 83 |

84 |

85 | don’t have an account yet? 86 |

87 | 88 | 91 | 92 |
93 |
94 | {% endblock %} 95 | -------------------------------------------------------------------------------- /label_buddy/templates/account/logout.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} ◦ Sign Out {% endblock %} 4 | 5 | {% block content %} 6 | {{ block.super }} 7 |
8 |
9 |
10 |

Sign Out

11 |
12 |

Are you sure you want to sign out?

13 |
14 | {% csrf_token %} 15 | {% if redirect_field_value %} 16 | 17 | {% endif %} 18 | 19 | 20 | No 21 |
22 |
23 |
24 |
25 | 26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /label_buddy/templates/account/messages/cannot_delete_primary_email.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans %}You cannot remove your primary e-mail address ({{email}}).{% endblocktrans %} 3 | -------------------------------------------------------------------------------- /label_buddy/templates/account/messages/email_confirmation_sent.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans %}Confirmation e-mail sent to {{email}}.{% endblocktrans %} 3 | -------------------------------------------------------------------------------- /label_buddy/templates/account/messages/email_confirmed.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans %}You have confirmed {{email}}.{% endblocktrans %} 3 | -------------------------------------------------------------------------------- /label_buddy/templates/account/messages/email_deleted.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans %}Removed e-mail address {{email}}.{% endblocktrans %} 3 | -------------------------------------------------------------------------------- /label_buddy/templates/account/messages/logged_in.txt: -------------------------------------------------------------------------------- 1 | {% load account %} 2 | {% load i18n %} 3 | {% user_display user as name %} 4 | {% blocktrans %}Successfully signed in as {{name}}.{% endblocktrans %} 5 | -------------------------------------------------------------------------------- /label_buddy/templates/account/messages/logged_out.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans %}You have signed out.{% endblocktrans %} 3 | -------------------------------------------------------------------------------- /label_buddy/templates/account/messages/password_changed.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans %}Password successfully changed.{% endblocktrans %} 3 | -------------------------------------------------------------------------------- /label_buddy/templates/account/messages/password_set.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans %}Password successfully set.{% endblocktrans %} 3 | -------------------------------------------------------------------------------- /label_buddy/templates/account/messages/primary_email_set.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans %}Primary e-mail address set.{% endblocktrans %} 3 | -------------------------------------------------------------------------------- /label_buddy/templates/account/messages/unverified_primary_email.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans %}Your primary e-mail address must be verified.{% endblocktrans %} 3 | -------------------------------------------------------------------------------- /label_buddy/templates/account/password_change.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %} ◦ Change Password{% endblock %} 6 | 7 | {% block breadcrumbs %} 8 | 13 | {% endblock %} 14 | 15 | {% block content %} 16 | {{ block.super }} 17 |
18 |
19 |
20 |

Change Password

21 |
22 |
23 | {% csrf_token %} 24 | {{ form | crispy}} 25 | 26 |
27 |
28 |
29 |
30 | 31 | 32 | {% endblock %} 33 | -------------------------------------------------------------------------------- /label_buddy/templates/account/password_reset.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load static %} 4 | 5 | {% load i18n %} 6 | 7 | {% load account %} 8 | 9 | {% block head_files %} 10 | {{ block.super }} 11 | 12 | {% endblock %} 13 | 14 | {% block title %} ◦ Password Reset {% endblock %} 15 | 16 | {% block navbar %}{% endblock %} 17 | 18 | {% block content %} 19 | {% if user.is_authenticated %} 20 | {% include "account/snippets/already_logged_in.html" %} 21 | {% endif %} 22 | {{ block.super }} 23 |
24 | 25 | Logo 26 |
27 |

Password Reset

28 |
29 | {% csrf_token %} 30 | {% for field in form %} 31 |
32 | 33 | {{ field }} 34 | {% if field.errors %} 35 | {% for error in field.errors %} 36 | 37 | 38 | {{ error }} 39 | 40 | {% endfor %} 41 | {% endif %} 42 |
43 | {% endfor %} 44 | 45 |
46 |
47 |
48 | 49 | 52 | 53 |

54 | • 55 |

56 |

57 | don't have an account? 58 |

59 | 60 | 63 | 64 |
65 |
66 | 67 | {% endblock %} 68 | -------------------------------------------------------------------------------- /label_buddy/templates/account/password_reset_done.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% load account %} 6 | 7 | {% block title %} ◦ Password Reset {% endblock %} 8 | 9 | {% block content %} 10 | {{ block.super }} 11 |
12 |
13 |
14 |

{% trans "Password Reset Email Sent." %}

15 | {% if user.is_authenticated %} 16 | {% include "account/snippets/already_logged_in.html" %} 17 | {% block content_extra %} {% endblock %} 18 | {% else %} 19 |

We have sent you an e-mail. Please contact us if you do not receive it within a few minutes.
You will be redirected to Sign In page in 5 seconds.

20 | 23 | {% endif %} 24 |
25 |
26 |
27 | 28 | 29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /label_buddy/templates/account/password_reset_from_key.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% load crispy_forms_tags %} 6 | 7 | {% block title %} ◦ Reset Password {% endblock %} 8 | 9 | {% block content %} 10 | {{ block.super }} 11 |
12 |
13 |
14 |

{% if token_fail %}{% trans "Bad Token" %}{% else %}{% trans "Change Password" %}{% endif %}

15 |
16 | {% if token_fail %} 17 | {% url 'account_reset_password' as passwd_reset_url %} 18 |

{% blocktrans %}The password reset link was invalid, possibly because it has already been used. Please request a new password reset.{% endblocktrans %}

19 | {% else %} 20 | {% if form %} 21 |
22 | {% csrf_token %} 23 | {{ form | crispy}} 24 | 25 |
26 | {% else %} 27 |

{% trans 'Your password is now changed.' %}

28 | {% endif %} 29 | {% endif %} 30 |
31 |
32 |
33 | {% endblock %} 34 | -------------------------------------------------------------------------------- /label_buddy/templates/account/password_reset_from_key_done.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block title %} ◦ Change Password {% endblock %} 6 | 7 | {% block content %} 8 | {{ block.super }} 9 |

{% trans "Change Password" %}

10 |

{% trans 'Your password is now changed.' %}

11 | 14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /label_buddy/templates/account/password_set.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block title %} ◦ Set Password {% endblock %} 6 | 7 | {% block content %} 8 | {{ block.super }} 9 |
10 |
11 |
12 |

{% trans "Set Password" %}

13 |
14 |
15 | {% csrf_token %} 16 | {{ form.as_p }} 17 | 18 |
19 |
20 |
21 |
22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /label_buddy/templates/account/signup.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% load static %} 4 | 5 | {% block head_files %} 6 | {{ block.super }} 7 | 8 | 9 | {% endblock %} 10 | 11 | {% block title %} ◦ Sign Up {% endblock %} 12 | 13 | {% block navbar %}{% endblock %} 14 | 15 | {% block content %} 16 | {{ block.super }} 17 |
18 | 19 | Logo 20 | 21 |
22 |

Sign up

23 | {% if form.errors %} 24 | {% for error in form.non_field_errors %} 25 |
26 | {{ error|escape }} 27 |
28 | {% endfor %} 29 | {% endif %} 30 | 31 |
32 | {% csrf_token %} 33 | {% for field in form %} 34 |
35 | 36 | {% if field.name == "password1" %} 37 |
38 | {{ field }} 39 |
40 |
41 | 42 | 43 | 44 |
45 |
46 |
47 | {% else %} 48 | {{ field }} 49 | {% endif %} 50 | {% if field.errors %} 51 | {% for error in field.errors %} 52 | 53 | 54 | {{ error }} 55 | 56 | {% endfor %} 57 | {% else %} 58 | {% if field.name == "password1" %} 59 | 60 | 61 | Min 8 characters, at least one Capital letter, at least one number, at least one symbol (@, #, $, etc.) 62 | 63 | {% endif %} 64 | {% endif %} 65 |
66 | {% endfor %} 67 | 68 |
69 |
70 |
71 | 72 | 75 | 76 |

77 | • 78 |

79 |

80 | already have an account? 81 |

82 | 83 | 86 | 87 |
88 |
89 | {% endblock %} 90 | 91 | -------------------------------------------------------------------------------- /label_buddy/templates/account/signup_closed.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block title %} ◦ Sign Up Closed {% endblock %} 6 | 7 | {% block content %} 8 | {{ block.super }} 9 |

{% trans "Sign Up Closed" %}

10 | 11 |

{% trans "We are sorry, but the sign up is currently closed." %}

12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /label_buddy/templates/account/snippets/already_logged_in.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% load account %} 4 | 5 | 6 | {% user_display user as user_display %} 7 |
8 | 9 |

Already Logged in!

10 |

You are already logged in as

{{ user_display }}

.

11 | Back to home 12 | 13 |
-------------------------------------------------------------------------------- /label_buddy/templates/account/verification_sent.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block title %} ◦ Verify Your E-mail Address {% endblock %} 6 | 7 | {% block content %} 8 | {{ block.super }} 9 |

{% trans "Verify Your E-mail Address" %}

10 | 11 |

{% blocktrans %}We have sent an e-mail to you for verification. Follow the link provided to finalize the signup process. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}

12 | 13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /label_buddy/templates/account/verified_email_required.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | 6 | {% block title %} ◦ Verify Your E-mail Address {% endblock %} 7 | 8 | {% block content %} 9 | {{ block.super }} 10 |

{% trans "Verify Your E-mail Address" %}

11 | 12 | {% url 'account_email' as email_url %} 13 | 14 |

{% blocktrans %}This part of the site requires us to verify that 15 | you are who you claim to be. For this purpose, we require that you 16 | verify ownership of your e-mail address. {% endblocktrans %}

17 | 18 |

{% blocktrans %}We have sent an e-mail to you for 19 | verification. Please click on the link inside this e-mail. Please 20 | contact us if you do not receive it within a few minutes.{% endblocktrans %}

21 | 22 |

{% blocktrans %}Note: you can still change your e-mail address.{% endblocktrans %}

23 | 24 | 25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /label_buddy/templates/annotation_page_modal.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /label_buddy/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | {% block head_files %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | {% endblock %} 32 | 33 | 34 | {% block title %}{% endblock %} 35 | 36 | 37 | 38 | 39 | {% block navbar %} 40 | 91 | {% endblock %} 92 | 93 | {% block content %} 94 | {% for message in messages %} 95 | {% if message.level == DEFAULT_MESSAGE_LEVELS.SUCCESS %} 96 | 102 | {% endif %} 103 | {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %} 104 | 110 | {% endif %} 111 | {% if message.level == DEFAULT_MESSAGE_LEVELS.WARNING %} 112 | 118 | {% endif %} 119 | {% endfor %} 120 | 121 | {% endblock %} 122 | 123 | {% block footer %} 124 | 125 |
126 | 127 | {% if user.is_authenticated %} 128 |
137 | 138 | {% endblock %} 139 | 140 | 141 | -------------------------------------------------------------------------------- /label_buddy/templates/export_modal.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /label_buddy/templates/import_modal.html: -------------------------------------------------------------------------------- 1 | {% load crispy_forms_tags %} 2 | 3 | 4 | -------------------------------------------------------------------------------- /label_buddy/templates/index_modal.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /label_buddy/templates/label_buddy/create_project.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load static %} 4 | 5 | {% load crispy_forms_tags %} 6 | 7 | {% block title %} ◦ Create Project{% endblock %} 8 | 9 | {% block breadcrumbs %} 10 | 16 | {% endblock %} 17 | 18 | {% block content %} 19 | {{ block.super }} 20 |
21 |
22 |
23 |

Create project

24 |
25 |
26 | {% csrf_token %} 27 | {{ form | crispy}} 28 | 29 | Cancel 30 |
31 |
32 |
33 |
34 | {% endblock %} -------------------------------------------------------------------------------- /label_buddy/templates/label_buddy/delete_annotation.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load static %} 4 | 5 | {% block head_files %} 6 | {{ block.super }} 7 | 8 | {% endblock %} 9 | 10 | {% block title %} ◦ Delete annotation {% endblock %} 11 | 12 | {% block breadcrumbs %} 13 | 31 | {% endblock %} 32 | 33 | {% block content %} 34 | {{ block.super }} 35 |
36 |
37 |
38 |

Delete annotation for {{ task.original_file_name }}

39 |
40 |

Are you sure you want to delete this annotation?

41 |
42 | {% csrf_token %} 43 | {% if redirect_field_value %} 44 | 45 | {% endif %} 46 | 47 | 48 | No 49 |
50 |
51 |
52 |
53 | 54 | {% endblock %} -------------------------------------------------------------------------------- /label_buddy/templates/label_buddy/delete_project.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} ◦ Delete {{ project.title }} {% endblock %} 4 | 5 | {% block breadcrumbs %} 6 | 19 | {% endblock %} 20 | 21 | {% block content %} 22 | {{ block.super }} 23 |
24 |
25 |
26 |

Delete project

27 |
28 |

Are you sure you want to delete {{ project.title }}?

29 |
30 | {% csrf_token %} 31 | {% if redirect_field_value %} 32 | 33 | {% endif %} 34 | 35 | 36 | No 37 |
38 |
39 |
40 |
41 | 42 | {% endblock %} 43 | -------------------------------------------------------------------------------- /label_buddy/templates/label_buddy/delete_task.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load static %} 4 | 5 | {% block head_files %} 6 | {{ block.super }} 7 | 8 | {% endblock %} 9 | 10 | {% block title %} ◦ Delete task {% endblock %} 11 | 12 | {% block breadcrumbs %} 13 | 27 | {% endblock %} 28 | 29 | {% block content %} 30 | {{ block.super }} 31 |
32 |
33 |
34 |

Delete task {{task.original_file_name}}

35 |
36 |

Are you sure you want to delete this task?

37 |
38 | {% csrf_token %} 39 | {% if redirect_field_value %} 40 | 41 | {% endif %} 42 | 43 | 44 | No 45 |
46 |
47 |
48 |
49 | 50 | {% endblock %} -------------------------------------------------------------------------------- /label_buddy/templates/label_buddy/edit_project.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load static %} 4 | 5 | {% block head_files%} 6 | {{ block.super }} 7 | 8 | 9 | {% endblock %} 10 | 11 | {% load crispy_forms_tags %} 12 | 13 | {% block title %} ◦ Edit {{ project.title }}{% endblock %} 14 | 15 | {% block breadcrumbs %} 16 | 29 | {% endblock %} 30 | 31 | {% block content %} 32 | {{ block.super }} 33 |
34 |
35 |
36 |

Edit project

37 |
38 |
39 | {% csrf_token %} 40 | {{ form | crispy}} 41 | 42 | Cancel 43 |
44 |
45 |
46 |
47 | {% endblock %} -------------------------------------------------------------------------------- /label_buddy/templates/label_buddy/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load static %} 4 | 5 | {% block head_files%} 6 | {{ block.super }} 7 | 8 | 9 | {% endblock %} 10 | 11 | {% block title %} Label Buddy {% endblock %} 12 | 13 | {% block breadcrumbs %} 14 | 17 | {% endblock %} 18 | 19 | {% block content %} 20 | {{ block.super }} 21 | {% if projects %} 22 |

23 | Projects 24 | 25 | 26 | 27 |

28 | {% if user.can_create_projects %} 29 |
30 | Create 31 |
32 | {% endif %} 33 | 34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | {% for project in page_obj %} 61 | 62 | 66 | 67 | 68 | {% if project.users_can_see_other_queues %} 69 | 74 | {% else %} 75 | 81 | {% endif %} 82 | 83 | 84 | 85 | 86 | {% if project.labels.count > 0 %} 87 | 88 | {% else %} 89 | 90 | {% endif %} 91 | 92 | {% if user in project.annotators.all %} 93 | 96 | {% else %} 97 | 100 | {% endif %} 101 | 102 | {% if user in project.reviewers.all %} 103 | 106 | {% else %} 107 | 110 | {% endif %} 111 | 118 | 119 | {% if user in project.managers.all %} 120 | 129 | {% else %} 130 | 131 | {% endif %} 132 | 133 | 134 | {% endfor %} 135 | 136 | 137 |
Project Title Date of CreationTask permissions 43 | 44 | 45 | 46 | Number of tasksAnnotationsNumber of labelsAnnotatorReviewerManagersOptions
63 | 64 | {{ project.title }} 65 | {{ project.created_at|date:"D d M Y" }} 70 | 71 | Tasks unassigned 72 | 73 | 76 | 77 | 78 | Tasks assigned 79 | 80 | {{ tasks_count|get_item:project.id }}{{ annotations_count|get_item:project.id }}{{ project.labels.count }}{{ project.labels.count }} 94 | Can annotate 95 | 98 | Cannot annotate 99 | 104 | Can review 105 | 108 | Cannot review 109 | 112 | {% if project.managers.count == 1%} 113 | {{ project.managers.all.0.email }} 114 | {% else %} 115 | {{ project.managers.count }} Managers 116 | {% endif %} 117 | 121 | 122 | 123 | 124 | / 125 | 126 | 127 | 128 | -
138 | 139 | 140 | 161 | 162 | {% else %} 163 |

No projects for you yet

164 | {% if user.can_create_projects %} 165 |
166 | Create 167 |
168 | {% endif %} 169 | {% endif %} 170 | 171 | {% if projects_count > projects_per_page %} 172 | {% include "pagination.html" %} 173 | {% endif %} 174 | {% include "index_modal.html" %} 175 | {% endblock %} -------------------------------------------------------------------------------- /label_buddy/templates/label_buddy/list_annotations.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load static %} 4 | 5 | {% block head_files%} 6 | {{ block.super }} 7 | 8 | 9 | {% endblock %} 10 | 11 | {% block title %} ◦ Task {{task.id}} annotations {% endblock %} 12 | 13 | {% block breadcrumbs %} 14 | 27 | {% endblock %} 28 | 29 | {% block content %} 30 | {{ block.super }} 31 |

Choose annotation to Review 32 | 33 |

34 |
35 | {% comment %} 36 | 39 | {% endcomment %} 40 | {% include "list_annotations_filters.html" %} 41 |
42 | 43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | {% for annotation in page_obj %} 64 | 65 | 66 | 67 | {% if task.file %} 68 | 69 | {% else %} 70 | 71 | {% endif %} 72 | 73 | 84 | 85 | 86 | 87 | 94 | 95 | 98 | 99 | 111 | 112 | 121 | 122 | {% endfor %} 123 | 124 |
IDTask Title AnnotatorDate of CreationLast updateResult JSON formatReview statusReviewed by you
{% get_table_id page_obj.number annotations_per_page forloop.counter %}{{ task.original_file_name }}- 74 | {% comment %} 75 | {% if annotation.user == user %} 76 | 77 | {% else %} 78 | 79 | {% endif %} 80 | {% endcomment %} 81 | 82 | {{ annotation.user.email }} 83 | {{ annotation.created_at|date:"D d M Y" }} 88 | {% if annotation.updated_at %} 89 | {{ annotation.updated_at|date:"D d M Y" }} 90 | {% else %} 91 | - 92 | {% endif %} 93 | 96 | {{ annotation.result }} 97 | 100 | {% if annotation.review_status == approved %} 101 | 102 | Approved 103 | {% elif annotation.review_status == rejected%} 104 | 105 | Rejected 106 | {% else %} 107 | 108 | Unreviewed 109 | {% endif %} 110 | 113 | {% if annotations_reviewed_by_user|get_item:annotation.id %} 114 | 115 | Reviewed by you 116 | {% else %} 117 | 118 | Not reviewed by you 119 | {% endif %} 120 |
125 |
126 | {% include "review_info_modal.html" %} 127 | {% if annotations_count > annotations_per_page %} 128 | {% include "pagination.html" %} 129 | {% endif %} 130 | {% endblock %} -------------------------------------------------------------------------------- /label_buddy/templates/label_buddy/user_edit_profile.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load static %} 4 | 5 | {% load crispy_forms_tags %} 6 | 7 | {% block title %} ◦ Edit profile{% endblock %} 8 | 9 | {% block breadcrumbs %} 10 | 15 | {% endblock %} 16 | 17 | {% block content %} 18 | {{ block.super }} 19 |
20 |
21 |
22 |

Edit Profile

23 |
24 |
25 | {% csrf_token %} 26 | {{ form | crispy}} 27 | 28 | Cancel 29 |
30 |
31 |
32 |
33 | {% endblock %} -------------------------------------------------------------------------------- /label_buddy/templates/label_buddy/welcome_page.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% load static %} 4 | 5 | {% block head_files %} 6 | {{ block.super }} 7 | 8 | 9 | {% endblock %} 10 | 11 | {% block title %} ◦ Welcome {% endblock %} 12 | 13 | {% block navbar %}{% endblock %} 14 | {% block content %} 15 |
16 | WELCOME TO 17 | Logo 18 |
19 | {% endblock %} 20 | {% block footer %}{% endblock %} -------------------------------------------------------------------------------- /label_buddy/templates/list_annotations_filters.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /label_buddy/templates/pagination.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /label_buddy/templates/project_information_modal.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /label_buddy/templates/project_modals.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /label_buddy/templates/project_page_filters.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /label_buddy/templates/review_info_modal.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /label_buddy/templates/review_page_modal.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /label_buddy/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/tests/__init__.py -------------------------------------------------------------------------------- /label_buddy/tests/test_pages.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase, Client 2 | 3 | class TestIndexPage(TestCase): 4 | def test_index(self): 5 | request = self.client.get('/') 6 | self.assertEqual(request.status_code, 200) 7 | 8 | def test_invalid_page(self): 9 | # test page that does not exist 10 | request = self.client.get('/does_not_exist') 11 | self.assertEqual(request.status_code, 404) -------------------------------------------------------------------------------- /label_buddy/users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eellak/gsoc2021-audio-annotation-tool/f31b9f937a26179e751bdc7eb0b42a1b1ece1dd2/label_buddy/users/__init__.py -------------------------------------------------------------------------------- /label_buddy/users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth.models import Group 3 | 4 | # Relative import 5 | from .models import User 6 | 7 | 8 | class UsersAdmin(admin.ModelAdmin): 9 | 10 | """ 11 | User class for the admin site. list_display shows the fields 12 | displayed in the admin site. 13 | """ 14 | 15 | search_fields = ["email", "username"] 16 | exclude = ( 17 | "user_permissions", 18 | "title", 19 | "groups", 20 | "password", 21 | "last_login", 22 | "is_featured", 23 | "location", 24 | "media_count", 25 | "is_active", 26 | ) 27 | list_display = [ 28 | "id", 29 | "name", 30 | "username", 31 | "email", 32 | "first_name", 33 | "last_name", 34 | "can_create_projects", 35 | "phone_number", 36 | "avatar", 37 | "date_joined", 38 | ] 39 | list_filter = ["can_create_projects", ] 40 | ordering = ("id",) 41 | 42 | 43 | admin.site.register(User, UsersAdmin) 44 | admin.site.unregister(Group) # Remove groups from admin 45 | -------------------------------------------------------------------------------- /label_buddy/users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UsersConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'users' 7 | -------------------------------------------------------------------------------- /label_buddy/users/forms.py: -------------------------------------------------------------------------------- 1 | from allauth.account.forms import SignupForm, LoginForm, ResetPasswordForm 2 | from django import forms 3 | from .models import User 4 | 5 | 6 | class ExtendedLogInForm(LoginForm): 7 | 8 | """ 9 | An extended Log In form from Djano-allauth for adding custom styling. 10 | """ 11 | 12 | def __init__(self, *args, **kwargs): 13 | super().__init__(*args, **kwargs) 14 | self.fields["login"].widget.attrs = {'class': 'myInput form-control', 'placeholder': "Email or Username", 'autocomplete': "email"} 15 | self.fields["password"].widget.attrs = {'class': 'myInput form-control', 'placeholder': "Password", 'autocomplete': "current-password"} 16 | 17 | 18 | class ExtendedSignUpForm(SignupForm): 19 | 20 | """ 21 | An extended Sign up form from Djano-allauth for adding custom styling. 22 | """ 23 | 24 | name = forms.CharField(max_length=256) 25 | ordered_field_names = ['name', 'email', 'username', 'password1'] 26 | 27 | def __init__(self, *args, **kwargs): 28 | # Call the init of the parent class 29 | super().__init__(*args, **kwargs) 30 | 31 | self.fields["name"].widget.attrs = {'class': 'myInput form-control', 'placeholder': 'E.g. "John Anderson"', 'autocomplete': "name"} 32 | self.fields["name"].label = "First & Last Name" 33 | 34 | self.fields["email"].widget.attrs = {'class': 'myInput form-control', 'placeholder': "E.g. JohnAnderson@mars.co", 'autocomplete': "email"} 35 | self.fields["email"].label = "Email Address*" 36 | 37 | self.fields["username"].widget.attrs = {'class': 'myInput form-control', 'placeholder': "E.g. johnanderson", 'autocomplete': "username"} 38 | self.fields["username"].label = "Username*" 39 | 40 | self.fields["password1"].widget.attrs = {'class': 'myInput form-control', 'placeholder': "Enter new password", 'autocomplete': "new-password"} 41 | self.fields["password1"].label = "Password*" 42 | self.rearrange_field_order() 43 | 44 | # Push error class if an error has occured 45 | for field in self: 46 | if field.errors: 47 | self.fields[field.name].widget.attrs["class"] += " error" 48 | 49 | def save(self, request): 50 | 51 | """ 52 | Modify save method to save the name given by the user. 53 | """ 54 | 55 | user = super(ExtendedSignUpForm, self).save(request) 56 | user.name = self.cleaned_data["name"] 57 | user.save() 58 | return user 59 | 60 | def rearrange_field_order(self): 61 | 62 | """ 63 | Change the order that the fields are displayed in the Sign up page. 64 | """ 65 | 66 | original_fields = self.fields 67 | new_fields = {} 68 | 69 | for field_name in self.ordered_field_names: 70 | field = original_fields.get(field_name) 71 | if field: 72 | new_fields[field_name] = field 73 | 74 | self.fields = new_fields 75 | 76 | 77 | class ExtendedResetPasswordForm(ResetPasswordForm): 78 | 79 | """ 80 | An extended Reset password form from Djano-allauth for adding custom styling. 81 | """ 82 | 83 | def __init__(self, *args, **kwargs): 84 | super().__init__(*args, **kwargs) 85 | self.fields["email"].widget.attrs = {'class': 'myInput form-control', 'placeholder': "JohnAnderson@mars.co", 'autocomplete': "email"} 86 | self.fields["email"].label = "Email*" 87 | 88 | 89 | class UserForm(forms.ModelForm): 90 | 91 | """ 92 | User form for edit profile page. 93 | """ 94 | 95 | class Meta: 96 | model = User 97 | fields = [ 98 | "name", 99 | "email", 100 | "phone_number", 101 | "avatar", 102 | ] 103 | labels = { 104 | 'name': 'Name:', 105 | 'email': 'Email:', 106 | 'phone_number': 'Phone number:', 107 | 'avatar': 'Avatar:', 108 | } 109 | 110 | def clean_avatar(self): 111 | 112 | """ 113 | Ensure that uploaded avatar is less than 2mb. 114 | """ 115 | 116 | image = self.cleaned_data.get("avatar", False) 117 | if image: 118 | if image.size > 2 * 1024 * 1024: 119 | raise forms.ValidationError("Image file too large ( > 2mb )") 120 | return image 121 | else: 122 | raise forms.ValidationError("Please provide a logo") 123 | 124 | def clean_email(self): 125 | 126 | """ 127 | User is not able to modify the email field in the edit profile page. 128 | """ 129 | 130 | # When a field is cleaned, we always return the existing model field 131 | return self.instance.email 132 | -------------------------------------------------------------------------------- /label_buddy/users/migrations/.gitignore: -------------------------------------------------------------------------------- 1 | # exclude all files 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /label_buddy/users/models.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.conf import settings 4 | from django.core.files import File 5 | from django.db import models 6 | from django.db.models.signals import pre_save 7 | from django.dispatch import receiver 8 | from django.contrib.auth.models import AbstractUser 9 | 10 | 11 | # Create your models here 12 | class User(AbstractUser): 13 | 14 | """ 15 | User class inherited from Django User model. 16 | """ 17 | 18 | # Additional fields 19 | name = models.CharField(max_length=256, default="", db_index=True, help_text='Users full name') 20 | can_create_projects = models.BooleanField(default=False, help_text='True if the user can create projects (be a manager)') 21 | phone_number = models.CharField(max_length=256, blank=True, help_text="User's phone number") 22 | avatar = models.ImageField(upload_to='images', blank=True, help_text="User's avatar (image)") 23 | 24 | def __str__(self): 25 | 26 | """ 27 | Display users by username. 28 | """ 29 | 30 | return '%s' % (self.username) 31 | if not self.name and not self.email: 32 | return '%s' % (self.username) 33 | else: 34 | if self.name and self.email: 35 | return '%s - %s' % (self.name, self.email) 36 | elif self.name: 37 | return '%s' % (self.name) 38 | else: 39 | return '%s' % (self.email) 40 | 41 | def save(self, *args, **kwargs): 42 | 43 | """ 44 | Superusers will be able to create projects by default. 45 | """ 46 | 47 | if self.is_superuser: 48 | self.can_create_projects = True 49 | super(User, self).save(*args, **kwargs) 50 | 51 | 52 | @receiver(pre_save, sender=User) 53 | def auto_delete_file_on_change(sender, instance, **kwargs): 54 | 55 | """ 56 | Deletes old file from filesystem when corresponding user object is updated with new file. 57 | """ 58 | 59 | pk = instance.pk 60 | if not pk: 61 | return False 62 | 63 | try: 64 | old_avatar = User.objects.get(pk=pk).avatar 65 | except User.DoesNotExist: 66 | return False 67 | 68 | if old_avatar: 69 | new_avatar = instance.avatar 70 | if not old_avatar == new_avatar: 71 | if os.path.isfile(old_avatar.path): 72 | os.remove(old_avatar.path) 73 | 74 | 75 | @receiver(pre_save, sender=User) 76 | def set_users_avatar(sender, instance, **kwargs): 77 | 78 | """ 79 | If user's avatar is not specified set it to the unknown icon user. 80 | """ 81 | 82 | if not instance.avatar: 83 | user_avatar = open(os.path.join(settings.BASE_DIR, 'static/images/user/user.jpg'), "rb") 84 | instance.avatar.save('user.jpg', File(user_avatar)) 85 | -------------------------------------------------------------------------------- /label_buddy/users/password_validators.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from django.core.exceptions import ValidationError 4 | from django.utils.translation import ugettext as _ 5 | 6 | 7 | class NumberValidator(object): 8 | 9 | """ 10 | Custom password validator which ensures that the password will contain 11 | at least 1 digit. 12 | """ 13 | 14 | def validate(self, password, user=None): 15 | if not re.findall('\\d', password): 16 | raise ValidationError( 17 | _("The password must contain at least 1 digit, 0-9."), 18 | code='password_no_number', 19 | ) 20 | 21 | def get_help_text(self): 22 | return _( 23 | "Your password must contain at least 1 digit, 0-9." 24 | ) 25 | 26 | 27 | class UppercaseValidator(object): 28 | 29 | """ 30 | Custom password validator which ensures that the password will contain 31 | at least 1 uppercase letter, A-Z. 32 | """ 33 | 34 | def validate(self, password, user=None): 35 | if not re.findall('[A-Z]', password): 36 | raise ValidationError( 37 | _("The password must contain at least 1 uppercase letter, A-Z."), 38 | code='password_no_upper', 39 | ) 40 | 41 | def get_help_text(self): 42 | return _( 43 | "Your password must contain at least 1 uppercase letter, A-Z." 44 | ) 45 | 46 | 47 | class SymbolValidator(object): 48 | 49 | """ 50 | Custom password validator which ensures that the password will contain 51 | at least 1 symbol. 52 | """ 53 | 54 | def validate(self, password, user=None): 55 | if not re.findall('[()[\\]{}|\\`~!@#$%^&*_\\-+=;:\'",<>./?]', password): 56 | raise ValidationError( 57 | _("The password must contain at least 1 symbol: " + "()[]{}|\\`~!@#$%^&*_-+=;:'\",<>./?"), code='password_no_symbol',) 58 | 59 | def get_help_text(self): 60 | return _( 61 | "Your password must contain at least 1 symbol: " + "()[]{}|\\`~!@#$%^&*_-+=;:'\",<>./?") 62 | -------------------------------------------------------------------------------- /label_buddy/users/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .models import User 3 | 4 | 5 | class UserSerializer(serializers.ModelSerializer): 6 | 7 | """ 8 | Serializer for user API endpoint data. 9 | """ 10 | 11 | class Meta: 12 | model = User 13 | fields = [ 14 | "name", 15 | "username", 16 | "email", 17 | ] 18 | -------------------------------------------------------------------------------- /label_buddy/users/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.conf.urls import url 3 | from rest_framework.urlpatterns import format_suffix_patterns 4 | from . import views 5 | 6 | # The API URLs are now determined automatically by the router 7 | urlpatterns = [ 8 | url(r"^user/(?P[\w@.]*)/edit$", views.edit_profile, name="edit_user"), 9 | # API VIEWS 10 | path('api/v1/users/', views.UserList.as_view(), name="user-list"), 11 | path('api/v1/users//', views.UserDetail.as_view(), name="specific_user"), 12 | ] 13 | 14 | urlpatterns = format_suffix_patterns(urlpatterns) 15 | -------------------------------------------------------------------------------- /label_buddy/users/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.contrib.auth.decorators import login_required 3 | from django.http import HttpResponseRedirect 4 | from django.contrib import messages 5 | 6 | from rest_framework.response import Response 7 | from rest_framework.views import APIView 8 | from rest_framework.exceptions import PermissionDenied 9 | from rest_framework import ( 10 | permissions, 11 | status, 12 | ) 13 | 14 | 15 | from .models import User 16 | from .serializers import UserSerializer 17 | from .forms import UserForm 18 | # Create your views here 19 | 20 | 21 | def get_user(username): 22 | 23 | """ 24 | Get user by username. 25 | """ 26 | 27 | try: 28 | user = User.objects.get(username=username) 29 | return user 30 | except User.DoesNotExist: 31 | return None 32 | 33 | 34 | @login_required 35 | def edit_profile(request, username): 36 | 37 | """ 38 | View for user edit profile. The user can update his/her information. 39 | """ 40 | 41 | context = {} 42 | user = get_user(username) 43 | if not user or (user != request.user): 44 | return HttpResponseRedirect("/") 45 | 46 | if request.method == "POST": 47 | form = UserForm(request.POST, request.FILES, instance=user) 48 | if form.is_valid(): 49 | new_user = form.save(commit=False) 50 | # Cannot change email 51 | new_user.email = user.email 52 | new_user.save() 53 | messages.add_message(request, messages.SUCCESS, "Successfully edited profile.") 54 | return HttpResponseRedirect("/") 55 | else: 56 | form = UserForm(instance=user) 57 | form.fields['email'].widget.attrs['readonly'] = True 58 | 59 | context["form"] = form 60 | return render(request, "label_buddy/user_edit_profile.html", context) 61 | 62 | 63 | # API VIEWS 64 | class UserList(APIView): 65 | 66 | """ 67 | List all users or create a new one. 68 | """ 69 | 70 | # User will be able to Post only if authenticated 71 | permission_classes = (permissions.IsAuthenticatedOrReadOnly,) 72 | serializer_class = UserSerializer 73 | 74 | # Get request 75 | def get(self, request, format=None): 76 | 77 | users = User.objects.all() 78 | serializer = UserSerializer(users, many=True) 79 | return Response(serializer.data) 80 | 81 | # Post request 82 | def post(self, request, format=None): 83 | serializer = UserSerializer(data=request.data) 84 | if serializer.is_valid(): 85 | serializer.save() 86 | return Response(serializer.data, status=status.HTTP_201_CREATED) 87 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 88 | 89 | 90 | class UserDetail(APIView): 91 | 92 | """ 93 | Retrieve, update or delete a user instance. 94 | """ 95 | 96 | # User will be able to Post only if authenticated 97 | permission_classes = (permissions.IsAuthenticatedOrReadOnly,) 98 | serializer_class = UserSerializer 99 | 100 | def get_object(self, pk): 101 | 102 | try: 103 | return User.objects.get(pk=pk) 104 | except PermissionDenied: 105 | return Response({"detail": "No permissions"}, status=status.HTTP_401_UNAUTHORIZED) 106 | except User.DoesNotExist: 107 | return Response({"detail": "User does not exist"}, status=status.HTTP_400_BAD_REQUEST) 108 | 109 | def get(self, request, pk, format=None): 110 | user = self.get_object(pk) 111 | serializer = UserSerializer(user) 112 | if isinstance(user, Response): 113 | return user 114 | 115 | return Response(serializer.data) 116 | 117 | def put(self, request, pk, format=None): 118 | user = self.get_object(pk) 119 | serializer = UserSerializer(user, data=request.data) 120 | if serializer.is_valid(): 121 | serializer.save() 122 | return Response(serializer.data) 123 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 124 | 125 | def delete(self, request, pk, format=None): 126 | user = self.get_object(pk) 127 | user.delete() 128 | return Response(status=status.HTTP_204_NO_CONTENT) 129 | -------------------------------------------------------------------------------- /pyvenv.cfg: -------------------------------------------------------------------------------- 1 | home = /usr 2 | implementation = CPython 3 | version_info = 3.8.5.final.0 4 | virtualenv = 20.0.17 5 | include-system-site-packages = false 6 | base-prefix = /usr 7 | base-exec-prefix = /usr 8 | base-executable = /usr/bin/python3 9 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.3.4 2 | backcall==0.2.0 3 | beautifulsoup4==4.9.3 4 | certifi==2021.5.30 5 | cffi==1.14.5 6 | chardet==4.0.0 7 | colorama==0.4.4 8 | confusable-homoglyphs==3.2.0 9 | cryptography==3.4.7 10 | cssutils==2.3.0 11 | decorator==5.0.9 12 | defusedxml==0.7.1 13 | Django==3.2.3 14 | django-allauth==0.44.0 15 | django-colorfield==0.4.1 16 | django-crispy-forms==1.12.0 17 | django-debug-toolbar==3.2.2 18 | django-enumchoicefield==2.0.0 19 | django-extensions==3.1.3 20 | django-jsonfield==1.4.1 21 | django-url-or-relative-url-field==0.1.2 22 | djangorestframework==3.12.4 23 | flake8==4.0.1 24 | future==0.18.2 25 | gunicorn==20.1.0 26 | idna==2.10 27 | importlib-metadata==4.6.4 28 | ipython==7.26.0 29 | ipython-genutils==0.2.0 30 | jedi==0.18.0 31 | matplotlib-inline==0.1.2 32 | mccabe==0.6.1 33 | oauthlib==3.1.1 34 | parso==0.8.2 35 | pexpect==4.8.0 36 | pickleshare==0.7.5 37 | Pillow==8.2.0 38 | prompt-toolkit==3.0.19 39 | psycopg2-binary==2.9.3 40 | ptyprocess==0.7.0 41 | pycodestyle==2.8.0 42 | pycparser==2.20 43 | pyflakes==2.4.0 44 | Pygments==2.10.0 45 | PyJWT==2.1.0 46 | pynliner==0.8.0 47 | python3-openid==3.2.0 48 | pytz==2021.1 49 | rarfile==4.0 50 | requests==2.25.1 51 | requests-oauthlib==1.3.0 52 | six==1.16.0 53 | soupsieve==2.2.1 54 | sqlparse==0.4.1 55 | traitlets==5.0.5 56 | typing-extensions==3.10.0.0 57 | urllib3==1.26.6 58 | wcwidth==0.2.5 59 | zipp==3.5.0 60 | --------------------------------------------------------------------------------