Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Deletion of Object #56

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions backend/migrations/versions/063b15fc4ca4_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""empty message

Revision ID: 063b15fc4ca4
Revises: b60bb67d1758
Create Date: 2021-05-02 16:32:52.230489

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '063b15fc4ca4'
down_revision = 'b60bb67d1758'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('data', sa.Column('is_deleted', sa.DateTime(), nullable=True))
op.add_column('label', sa.Column('is_deleted', sa.DateTime(), nullable=True))
op.add_column('user', sa.Column('is_deleted', sa.DateTime(), nullable=True))
op.add_column('user_project', sa.Column('is_deleted', sa.DateTime(), nullable=True))
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('user_project', 'is_deleted')
op.drop_column('user', 'is_deleted')
op.drop_column('label', 'is_deleted')
op.drop_column('data', 'is_deleted')
# ### end Alembic commands ###
28 changes: 28 additions & 0 deletions backend/migrations/versions/e1e28decd983_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""empty message

Revision ID: e1e28decd983
Revises: 063b15fc4ca4
Create Date: 2021-05-02 16:46:55.760634

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'e1e28decd983'
down_revision = '063b15fc4ca4'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('project', sa.Column('is_deleted', sa.DateTime(), nullable=True))
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('project', 'is_deleted')
# ### end Alembic commands ###
30 changes: 30 additions & 0 deletions backend/migrations/versions/ffc98d2cb07f_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""empty message

Revision ID: ffc98d2cb07f
Revises: e1e28decd983
Create Date: 2021-05-02 16:58:36.146492

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'ffc98d2cb07f'
down_revision = 'e1e28decd983'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('label_type', sa.Column('is_deleted', sa.DateTime(), nullable=True))
op.add_column('label_value', sa.Column('is_deleted', sa.DateTime(), nullable=True))
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('label_value', 'is_deleted')
op.drop_column('label_type', 'is_deleted')
# ### end Alembic commands ###
69 changes: 67 additions & 2 deletions backend/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,52 @@
from werkzeug.security import generate_password_hash, check_password_hash
from flask_sqlalchemy import BaseQuery
from backend import db, app


class QueryWithSoftDelete(BaseQuery):
_with_deleted = False

def __new__(cls, *args, **kwargs):
obj = super(QueryWithSoftDelete, cls).__new__(cls)
obj._with_deleted = kwargs.pop("_with_deleted", False)
if len(args) > 0:
super(QueryWithSoftDelete, obj).__init__(*args, **kwargs)
return obj.filter_by(is_deleted=None) if obj._with_deleted is None else obj
return obj

def __init__(self, *args, **kwargs):
pass

def with_deleted(self):
return self.__class__(
db.class_mapper(self._mapper_zero().class_),
session=db.session(),
_with_deleted=True,
)

def _get(self, *args, **kwargs):
# this calls the original query.get function from the base class
return super(QueryWithSoftDelete, self).get(*args, **kwargs)

def get(self, *args, **kwargs):
# the query.get method does not like it if there is a filter clause
# pre-loaded, so we need to implement it using a workaround
obj = self.with_deleted()._get(*args, **kwargs)
return (
obj if obj is None or self._with_deleted or obj.is_deleted is None else None
)

def _all(self, *args, **kwargs):
return super(QueryWithSoftDelete, self).all(*args, **kwargs)

def all(self, *args, **kwargs):
objs = self.with_deleted()._all(*args, **kwargs)
ret_vals = []
for obj in objs:
if obj and obj.is_deleted is None:
ret_vals.append(obj)
return ret_vals

from backend import db

annotation_table = db.Table(
"annotation",
Expand Down Expand Up @@ -39,14 +85,22 @@
default=db.func.now(),
onupdate=db.func.utc_timestamp(),
),
db.Column(
"is_deleted",
db.DateTime(),
nullable=True,
default=None,
),
)


class Data(db.Model):
__tablename__ = "data"
query_class = QueryWithSoftDelete

id = db.Column("id", db.Integer(), primary_key=True)

is_deleted = db.Column("is_deleted", db.DateTime(), nullable=True, default=None)
project_id = db.Column(
"project_id", db.Integer(), db.ForeignKey("project.id"), nullable=False
)
Expand Down Expand Up @@ -108,8 +162,10 @@ def to_dict(self):

class Label(db.Model):
__tablename__ = "label"
query_class = QueryWithSoftDelete

id = db.Column("id", db.Integer(), primary_key=True)
is_deleted = db.Column("is_deleted", db.DateTime(), nullable=True, default=None)

name = db.Column("name", db.String(32), nullable=False)

Expand Down Expand Up @@ -162,13 +218,16 @@ class LabelType(db.Model):
default=db.func.now(),
onupdate=db.func.utc_timestamp(),
)
is_deleted = db.Column("is_deleted", db.DateTime(), nullable=True, default=None)


class LabelValue(db.Model):
__tablename__ = "label_value"

id = db.Column("id", db.Integer(), primary_key=True)

is_deleted = db.Column("is_deleted", db.DateTime(), nullable=True, default=None)

label_id = db.Column(
"label_id", db.Integer(), db.ForeignKey("label.id"), nullable=False
)
Expand Down Expand Up @@ -225,6 +284,8 @@ class Project(db.Model):
onupdate=db.func.utc_timestamp(),
)

is_deleted = db.Column("is_deleted", db.DateTime(), nullable=True, default=None)

users = db.relationship(
"User", secondary=user_project_table, back_populates="projects"
)
Expand Down Expand Up @@ -281,7 +342,9 @@ class Segmentation(db.Model):
)

values = db.relationship(
"LabelValue", secondary=annotation_table, back_populates="segmentations",
"LabelValue",
secondary=annotation_table,
back_populates="segmentations",
)

def set_start_time(self, start_time):
Expand All @@ -305,6 +368,7 @@ def to_dict(self):

class User(db.Model):
__tablename__ = "user"
query_class = QueryWithSoftDelete

id = db.Column("id", db.Integer(), primary_key=True)

Expand Down Expand Up @@ -334,6 +398,7 @@ class User(db.Model):
projects = db.relationship(
"Project", secondary=user_project_table, back_populates="users"
)
is_deleted = db.Column("is_deleted", db.DateTime(), nullable=True, default=None)

def set_role(self, role_id):
self.role_id = role_id
Expand Down
5 changes: 4 additions & 1 deletion backend/routes/current_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def fetch_data_for_project(project_id):

data["pending"] = (
db.session.query(Data)
.filter(Data.is_deleted == None)
.filter(Data.assigned_user_id == request_user.id)
.filter(Data.project_id == project_id)
.filter(Data.id.notin_(segmentations))
Expand All @@ -68,6 +69,7 @@ def fetch_data_for_project(project_id):

data["completed"] = (
db.session.query(Data)
.filter(Data.is_deleted == None)
.filter(Data.assigned_user_id == request_user.id)
.filter(Data.project_id == project_id)
.filter(Data.id.in_(segmentations))
Expand All @@ -77,12 +79,13 @@ def fetch_data_for_project(project_id):

data["marked_review"] = Data.query.filter_by(
assigned_user_id=request_user.id,
is_deleted=None,
project_id=project_id,
is_marked_for_review=True,
).order_by(Data.last_modified.desc())

data["all"] = Data.query.filter_by(
assigned_user_id=request_user.id, project_id=project_id
assigned_user_id=request_user.id, project_id=project_id, is_deleted=None
).order_by(Data.last_modified.desc())

paginated_data = data[active].paginate(page, 10, False)
Expand Down
41 changes: 41 additions & 0 deletions backend/routes/data.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import json
import sqlalchemy as sa
import uuid
Expand Down Expand Up @@ -190,3 +191,43 @@ def add_data():
),
201,
)


@api.route("/rmdata", methods=["POST"])
@jwt_required
def remove_data():
identity = get_jwt_identity()
request_user = User.query.filter_by(username=identity["username"]).first()
is_admin = True if request_user.role.role == "admin" else False
if is_admin == False:
return jsonify(message="Unauthorized access!"), 401

if not request.is_json:
return jsonify(message="Missing JSON in request"), 400

data_id = request.json['dataId']
project_id = request.json['projectId']

app.logger.info(f"This is something: {data_id} : {project_id}")

try:
data = Data.query.filter_by(id=data_id, project_id=project_id).first()
data.is_deleted = sa.func.now()

# for segment in Segmentation.query.filter_by(data_id=data.id):
# segment.is_deleted = sa.func.now()

except Exception as e:
app.logger.error("Error deleting datapoint")
app.logger.error(e)
return jsonify(message="Error deleting datapoint!"), 500

db.session.commit()

return (
jsonify(
message=f"Data deleted",
type="DATA_DELETED",
),
201,
)
29 changes: 28 additions & 1 deletion backend/routes/labels.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def add_value_to_label(label_id):

try:
label_value = LabelValue(value=value, label_id=label_id)
app.logger.info(f"soemthing was done here: {label_value}")
db.session.add(label_value)
db.session.commit()
db.session.refresh(label_value)
Expand Down Expand Up @@ -78,7 +79,7 @@ def get_values_for_label(label_id):
return jsonify(message="Unauthorized access!"), 401

try:
values = LabelValue.query.filter_by(label_id=label_id).all()
values = LabelValue.query.filter_by(label_id=label_id, is_deleted=None).all()
response = [
{
"value_id": value.id,
Expand All @@ -95,6 +96,32 @@ def get_values_for_label(label_id):
return (jsonify(values=response), 200)


@api.route("/labels/<int:label_id>/values/<int:label_value_id>", methods=["DELETE"])
@jwt_required
def remove_values_from_label(label_id, label_value_id):
app.logger.info("We are deleint something")
identity = get_jwt_identity()
request_user = User.query.filter_by(username=identity["username"]).first()
is_admin = True if request_user.role.role == "admin" else False

if is_admin == False:
return jsonify(message="Unauthorized access!"), 401

try:
value = LabelValue.query.get(label_value_id)
value.is_deleted = db.func.now()
db.session.commit()
except Exception as e:
app.logger.error(f"No values exists for label with id: {label_id}")
app.logger.error(e)
return (jsonify(message=f"No values exists for label with id: {label_id}"), 404)

return (
jsonify(label_value_id=label_value_id, message=f"LabelValue was deleted"),
200,
)


@api.route("/labels/<int:label_id>/values/<int:label_value_id>", methods=["GET"])
@jwt_required
def fetch_label_value(label_id, label_value_id):
Expand Down
Loading