From 099da4abfe6c147e91b8f8ebd7192702ce750b0f Mon Sep 17 00:00:00 2001 From: Grace Date: Sat, 16 May 2020 16:39:57 -0700 Subject: [PATCH] Implemented Tags (#29) * Added Anurag 1:1 meeting * Meeting notes for 1:1 05-12-2020 * Updated frontend notes * Create 5-13-20-Jason-onboarding.md * 5-13-20-Jason-onboarding * Delete 5-13-20-Jason-onboarding.md * Update 5-13-20-Jason-onboarding.md * Update 5-13-20-Jason-onboarding.md * Updated folders to not break windows laptops * Initial commit * Added Tag model * Added emoji field * Added emoji field to Meta class * Implemented create_tag function * Implemented CreateTag * Implemented Update Tag function * Implemented FetchTags * Implemented TagFollowers * Removed comment * Added tag * Implemented Tag Routes, Models, Schemas, Decorators & Utils * Fixed typo for submission_guidelines --> submission_guideline * Updated comments * Added comment Detailed comment that explains many to many relationship to keep track of user id and tag id * Modified backref to back_populates for user and tag models * Added validate_form_data decorator to ensure sanitized data * Removed unused code * Updated relationships in Tag and User model * updated * Fixed name of typo in validate_tag_form decorator * Updated * Updated * Added toasted marshmallow.jit --- api/backend/__init__.py | 3 + api/backend/models.py | 26 ++++- api/backend/tags/decorators.py | 39 ++++++++ api/backend/tags/routes.py | 95 +++++++++++++++++++ api/backend/tags/schemas.py | 28 ++++++ api/backend/tags/utils.py | 24 +++++ .../one-on-ones/5-12-20-Anurag-onboarding.md | 36 +++++++ .../one-on-ones/5-13-20-Jason-onboarding.md | 34 +++++++ meetings/weekly/frontend/5-11-20.md | 13 ++- 9 files changed, 295 insertions(+), 3 deletions(-) create mode 100644 api/backend/tags/decorators.py create mode 100644 api/backend/tags/routes.py create mode 100644 api/backend/tags/schemas.py create mode 100644 api/backend/tags/utils.py create mode 100644 meetings/one-on-ones/5-12-20-Anurag-onboarding.md create mode 100644 meetings/one-on-ones/5-13-20-Jason-onboarding.md diff --git a/api/backend/__init__.py b/api/backend/__init__.py index f427483..4d7a293 100644 --- a/api/backend/__init__.py +++ b/api/backend/__init__.py @@ -9,6 +9,7 @@ app = Flask(__name__) + app.config['GITHUB_CLIENT_ID'] = GITHUB_CLIENT_ID app.config['GITHUB_CLIENT_SECRET'] = GITHUB_CLIENT_SECRET app.config["PROPAGATE_EXCEPTIONS"] = True @@ -27,8 +28,10 @@ from ..backend.home.routes import home_bp from ..backend.meta.routes import meta_bp from ..backend.users.routes import user_bp +from ..backend.tags.routes import tag_bp app.register_blueprint(authentication_bp) app.register_blueprint(home_bp) app.register_blueprint(meta_bp) app.register_blueprint(user_bp) +app.register_blueprint(tag_bp) diff --git a/api/backend/models.py b/api/backend/models.py index d9a6e5d..7b6859f 100644 --- a/api/backend/models.py +++ b/api/backend/models.py @@ -1,6 +1,13 @@ from api.backend import db +# A many to many relationship to keep track of tags and users +user_tag_rel = db.Table("user_tag_rel", + db.Column("user_id", db.Integer, db.ForeignKey("user.id"), primary_key=True), + db.Column("tag_id", db.Integer, db.ForeignKey("tag.id"), primary_key=True) + ) + + class Meta(db.Model): id = db.Column(db.Integer, primary_key=True) roles = db.Column(db.Text, nullable=True) @@ -9,6 +16,7 @@ class Meta(db.Model): student = db.relationship("Student", uselist=False, cascade="all,delete", back_populates="meta") teacher = db.relationship("Teacher", uselist=False, cascade="all,delete", back_populates="meta") + def __repr__(self): return f"Meta('{self.id}')" @@ -24,7 +32,9 @@ class User(db.Model): image = db.Column(db.Text, nullable=True) meta_id = db.Column(db.Integer, db.ForeignKey('meta.id')) meta = db.relationship("Meta", back_populates="user") - + tags = db.relationship("Tag", secondary="user_tag_rel", + back_populates="users") + def __repr__(self): return f"User('{self.email}')" @@ -54,3 +64,17 @@ class Teacher(db.Model): def __repr__(self): return f"Teacher('{self.id}')" + + +class Tag(db.Model): + id = db.Column(db.Integer, primary_key=True, nullable=False) + name = db.Column(db.Text, nullable=False) + summary = db.Column(db.Text, nullable=False) + submission_guideline = db.Column(db.Text, nullable=False) + about = db.Column(db.Text, nullable=False) + emoji = db.Column(db.Text, nullable=False) + users = db.relationship("User", secondary="user_tag_rel", + back_populates="tags") + + def __repr__(self): + return f"Tag('{self.id} , {self.name}')" \ No newline at end of file diff --git a/api/backend/tags/decorators.py b/api/backend/tags/decorators.py new file mode 100644 index 0000000..118f05d --- /dev/null +++ b/api/backend/tags/decorators.py @@ -0,0 +1,39 @@ +from ..models import Tag +from functools import wraps +from ..tags.schemas import tag_form_schema +from flask import request + + +# Decorator to check if a tag exists +def tag_exists(f): + + @wraps(f) + def wrap(*args, **kwargs): + tag = Tag.query.get(kwargs['tag_id']) + + if tag: + return f(*args, **kwargs) + else: + return { + "message": "Tag does not exist" + }, 404 + + return wrap + + +# Decorator to check if data in form is valid +def validate_tag_form(f): + + @wraps(f) + def wrap(*args, **kwargs): + form_data = request.get_json() + errors = tag_form_schema.validate(form_data) + + if errors: + return { + "message": "Unable to create tag. Please enter all required form inputs." + }, 422 + else: + return f(*args, **kwargs) + + return wrap diff --git a/api/backend/tags/routes.py b/api/backend/tags/routes.py new file mode 100644 index 0000000..559fbb0 --- /dev/null +++ b/api/backend/tags/routes.py @@ -0,0 +1,95 @@ +from .. import api, db +from ..models import Tag, User +from ..tags.decorators import tag_exists, validate_tag_form +from ..tags.schemas import tag_schema, tags_schema, tag_form_schema +from ..tags.utils import create_tag, update_tag +from flask import Blueprint, request, session +from flask_restful import Resource + +# Blueprint for tags +tag_bp = Blueprint("tags", __name__) + + +# Class to fetch and create tag +class Tags(Resource): + # Create Tag + @validate_tag_form + def post(self): + form_data = request.get_json() + tag = create_tag(form_data) + + db.session.add(tag) + db.session.commit() + + return { + "message": "Tag sucessfully created" + }, 201 + + + # Fetch Tags + def get(self): + tag = Tag.query.limit(15).all() + + if len(tag) != 0: + return tags_schema.dump(tag) + else: + return { + "message": "No Tags" + }, 404 + + +# Class for updating, deleting & fetching tags +class TagCRUD(Resource): + # Update specific tag + @validate_tag_form + @tag_exists + def put(self, tag_id): + tag = Tag.query.get(tag_id) + form_data = request.get_json() + update_tag(tag, form_data) + + db.session.commit() + + return { + "message": "Tag successfully updated" + }, 200 + + # Fetch specific tag + @tag_exists + def get(self, tag_id): + tag = Tag.query.get(tag_id) + + return tag_schema.dump(tag) + + # Delete specific tag + @tag_exists + def delete(self, tag_id): + tag = Tag.query.get(tag_id) + + db.session.delete(tag) + db.session.commit() + + return { + "message": "Tag successfully deleted" + }, 200 + + +# Class to update followers +class TagFollowers(Resource): + def put(self, tag_id): + user_data = session["profile"] + user = User.query.get(user_data["user_id"]) + tag = Tag.query.get(tag_id) + + tag.users.append(user) + db.session.commit() + + return { + "message": user.name + "is now following" + tag.name + }, 200 + + +# # Creates the routes for the classes +api.add_resource(Tags, "/api/tags") +api.add_resource(TagCRUD, "/api/tags/") +api.add_resource(TagFollowers, "/api/tags//followers") \ No newline at end of file diff --git a/api/backend/tags/schemas.py b/api/backend/tags/schemas.py new file mode 100644 index 0000000..ce0326a --- /dev/null +++ b/api/backend/tags/schemas.py @@ -0,0 +1,28 @@ +from marshmallow import Schema, fields +import toastedmarshmallow + + +# Validates tag form +class TagFormSchema(Schema): + name = fields.Str(required=True) + summary = fields.Str(required=True) + submission_guideline = fields.Str(required=True) + about = fields.Str(required=True) + emoji = fields.Str(required=True) + + class Meta: + # Fields to show when sending data + fields = ("name", "summary", "submission_guideline", "about", "emoji") + ordered = True + + +class TagSchema(Schema): + id = fields.Int(required=True) + + +tag_form_schema = TagFormSchema() +tag_schema = TagSchema() +tags_schema = TagSchema(many=True) +tag_form_schema.jit = toastedmarshmallow.Jit +tag_schema.jit = toastedmarshmallow.Jit +tags_schema.jit = toastedmarshmallow.Jit diff --git a/api/backend/tags/utils.py b/api/backend/tags/utils.py new file mode 100644 index 0000000..54a9a2b --- /dev/null +++ b/api/backend/tags/utils.py @@ -0,0 +1,24 @@ +from ..models import Tag + + +# Function to create a tag +def create_tag(form_data): + tag = Tag(name=form_data["name"], + summary=form_data["summary"], + submission_guideline=form_data["submission_guideline"], + about=form_data["about"], + emoji=form_data["emoji"] + ) + + return tag + + +# Function to update a tag +def update_tag(tag, form_data): + tag.name = form_data["name"] + tag.summary = form_data["summary"] + tag.submission_guideline = form_data["submission_guideline"] + tag.about = form_data["about"] + tag.emoji = form_data["emoji"] + + return \ No newline at end of file diff --git a/meetings/one-on-ones/5-12-20-Anurag-onboarding.md b/meetings/one-on-ones/5-12-20-Anurag-onboarding.md new file mode 100644 index 0000000..f95281c --- /dev/null +++ b/meetings/one-on-ones/5-12-20-Anurag-onboarding.md @@ -0,0 +1,36 @@ +# Meeting Purpose +Weekly meeting + +**Lead** +Bryan + +**Attendees** +* Anurag + +## Updates: +*What has been completed and can be checked off* + +* + +## Discussion Points: +*Ideas, feedback, concerns, plans* +* Understand the overview of the onboarding procedure +* Breakdown of tasks and expectations for the first week +* Understand what Engineering team does +Practice taking notes on Github during the 1:1 +* Please give your overview feedback and express what position you are within the Engineering team! +* Give onboarding feedback +* + +## Action Plan: +*Where to go next, dependencies, all deadlines* +*Bryan will talk to the People team to improve the onboarding process and have them delve more into what BitProject actually represents as an organization. +*Watch the remaining webinars and close this issue. + +## Deliverables: +*Within the next (timeframe)* +*Anurag will watch the webinars by Thursday and finish the onboarding checklist. + +Name | Assigned To | Deadline | Notes +------|-------------|----------|------ +Name | @ | May 20th | Explanation diff --git a/meetings/one-on-ones/5-13-20-Jason-onboarding.md b/meetings/one-on-ones/5-13-20-Jason-onboarding.md new file mode 100644 index 0000000..b4eeae6 --- /dev/null +++ b/meetings/one-on-ones/5-13-20-Jason-onboarding.md @@ -0,0 +1,34 @@ +# Meeting Purpose + +**Lead** +Bryan Wong + +**Attendees** +* Zexing(Jason) Fang + +## Updates: +*What has been completed and can be checked off* + +* Jason is almost done with onboarding checklist + +## Discussion Points: +*Ideas, feedback, concerns, plans* +* Understand the overview of the onboarding procedure +* Breakdown of tasks and expectations for the first week +* Understand what Engineering team does +* Practice taking notes on Github during the 1:1 +* Please give your overview feedback and express what position you are interested within the Engineering team! +* Give onboarding feedback +* Clear up the onboarding issue + + +## Action Plan: +*Where to go next, dependencies, all deadlines* +* None. Jason finished his onboarding checklist! + +## Deliverables: +*Within the next (timeframe)* + +Name | Assigned To | Deadline | Notes +------|-------------|----------|------ +Example 1 | @studentusername2 | Feb 30th | Explanation diff --git a/meetings/weekly/frontend/5-11-20.md b/meetings/weekly/frontend/5-11-20.md index f9be8cf..091996c 100644 --- a/meetings/weekly/frontend/5-11-20.md +++ b/meetings/weekly/frontend/5-11-20.md @@ -14,6 +14,9 @@ Bryan * Alex * Tom +**Video Link** +https://ucdavis.zoom.us/rec/share/5uBtLOnM93JLe8_J4Wfaeb4zO9jYeaa80HQcrPEPmB1VRx1WiBf18WdHLAjTfhvv + ## Updates: *What has been completed and can be checked off* @@ -30,11 +33,17 @@ Bryan ## Action Plan: *Where to go next, dependencies, all deadlines* -* Plan1 +* Mark will work on the Explore page +* Jason will work on User Dashboard +* Yuan will work on the Home page ## Deliverables: *Within the next (timeframe)* Name | Assigned To | Deadline | Notes ------|-------------|----------|------ -Example 1 | @studentusername2 | Feb 30th | Explanation +Mark | @mzp0625 | May 20th | Integrate the explore page +Jason | @JersinFong | May 20th | Integrate the User Dashboard page +Yuan | @VelForce | May 20th | Integrate the Home page +Ayush | @ayushbha | May 20th | Integrate the Markdown Editor +Aishwarya | @aishwarya-mustoori | May 20th | Integrate Article viewer page \ No newline at end of file