diff --git a/src/.gitignore b/src/.gitignore index b1630e79a..bb94c74ab 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -12,4 +12,5 @@ deployment/aws/ *.sqlite3* test_data/ celerybeat-schedule.db -*.rdb* \ No newline at end of file +*.rdb* +venv \ No newline at end of file diff --git a/src/api/handlers/action.py b/src/api/handlers/action.py index a5a9b85fe..7c5a0e196 100644 --- a/src/api/handlers/action.py +++ b/src/api/handlers/action.py @@ -6,6 +6,7 @@ #from types import FunctionType as function from _main_.utils.context import Context from api.decorators import admins_only, super_admins_only, login_required +from api.store.common import expect_media_fields class ActionHandler(RouteHandler): @@ -91,6 +92,7 @@ def submit(self, request): .expect("vendors", list, is_required=False) .expect("action_id", str, is_required=False) ) + self = expect_media_fields(self) args, err = self.validator.verify(args) if err: @@ -151,6 +153,8 @@ def update(self, request): .expect("vendors", list, is_required=False) ) + self = expect_media_fields(self) + args, err = self.validator.verify(args) if err: return err diff --git a/src/api/handlers/event.py b/src/api/handlers/event.py index a60b4aa99..d55ce6904 100644 --- a/src/api/handlers/event.py +++ b/src/api/handlers/event.py @@ -7,6 +7,7 @@ from types import FunctionType as function from _main_.utils.context import Context from api.decorators import admins_only, super_admins_only, login_required +from api.store.common import expect_media_fields class EventHandler(RouteHandler): @@ -221,6 +222,7 @@ def submit(self, request): self.validator.expect('rsvp_enabled', bool) self.validator.expect('rsvp_email', bool) self.validator.expect('event_id', str) + self = expect_media_fields(self) args, err = self.validator.verify(args) if err: @@ -310,6 +312,8 @@ def update(self, request): self.validator.expect("event_type",str) self.validator.expect("publicity_selections","str_list") self.validator.expect("shared_to","str_list") + + self = expect_media_fields(self) args, err = self.validator.verify(args) if err: diff --git a/src/api/handlers/media_library.py b/src/api/handlers/media_library.py index 9f3aa16a5..677e7e9ae 100644 --- a/src/api/handlers/media_library.py +++ b/src/api/handlers/media_library.py @@ -3,6 +3,7 @@ from api.decorators import admins_only from api.services.media_library import MediaLibraryService from _main_.utils.massenergize_response import MassenergizeResponse +from api.store.common import expect_media_fields class MediaLibraryHandler(RouteHandler): @@ -37,7 +38,7 @@ def fetch_content(self, request): return error return MassenergizeResponse(data=images) - @admins_only + @admins_only def search(self, request): """Filters images and only retrieves content related to a scope(events, testimonials,actions etc). More search types to be added later when requested...""" context: Context = request.context @@ -48,7 +49,7 @@ def search(self, request): "user_ids", "str_list" ).expect( "keywords", "str_list" - ) + ).expect("public", str) args, err = self.validator.verify(args, strict=True) if err: return err @@ -80,25 +81,14 @@ def addToGallery(self, request): args: dict = context.args self.validator.expect("user_id", str, is_required=True).expect( "community_ids", list - ).expect("title", str).expect("file", "file", is_required=True).expect( + ).expect("publicity", str).expect("title", str).expect( + "file", "file", is_required=True + ).expect( "is_universal", bool ).expect( "tags", "str_list" - ).expect( - "size", str - ).expect( - "size_text", str - ).expect( - "description" - ).expect( - "underAge", bool - ).expect( - "copyright", bool - ).expect( - "copyright_att", str - ).expect( - "guardian_info", str ) + self = expect_media_fields(self) args, err = self.validator.verify(args, strict=True) if err: return err @@ -137,14 +127,12 @@ def find_images(self, request): return error return MassenergizeResponse(data=response) - @admins_only + @admins_only def edit_details(self, request): """Saves changes to updated image details""" context: Context = request.context args: dict = context.args - self.validator.expect("description").expect("underAge", bool).expect( - "copyright", bool - ).expect("copyright_att", str).expect("guardian_info", str).expect( + self.validator.expect( "tags", "str_list" ).expect( "community_ids", list @@ -152,7 +140,8 @@ def edit_details(self, request): "media_id", int, is_required=True ).expect( "user_upload_id", int, is_required=True - ) + ).expect("publicity", str) + self = expect_media_fields(self) args, err = self.validator.verify(args, strict=True) if err: return err diff --git a/src/api/handlers/testimonial.py b/src/api/handlers/testimonial.py index 12aa98180..98a895816 100644 --- a/src/api/handlers/testimonial.py +++ b/src/api/handlers/testimonial.py @@ -8,6 +8,7 @@ from _main_.utils.context import Context from _main_.utils.validator import Validator from api.decorators import admins_only, super_admins_only, login_required +from api.store.common import expect_media_fields class TestimonialHandler(RouteHandler): @@ -94,6 +95,7 @@ def submit(self, request): self.validator.rename('preferredName', 'preferred_name') self.validator.expect('testimonial_id', str) self.validator.expect("image", "file", is_required=False) + self = expect_media_fields(self) args, err = self.validator.verify(args) if err: @@ -144,6 +146,8 @@ def update(self, request): self.validator.rename('action_id', 'action') self.validator.rename('vendor_id', 'vendor') self.validator.expect("image", "str_list") + + self = expect_media_fields(self) args, err = self.validator.verify(args) if err: diff --git a/src/api/handlers/vendor.py b/src/api/handlers/vendor.py index fa2e9837f..62946ff80 100644 --- a/src/api/handlers/vendor.py +++ b/src/api/handlers/vendor.py @@ -10,6 +10,7 @@ from _main_.utils.context import Context from _main_.utils.validator import Validator from api.decorators import admins_only, super_admins_only, login_required +from api.store.common import expect_media_fields @@ -107,7 +108,16 @@ def submit(self, request): .expect("location", str, is_required=False) .expect("vendor_id", str) - ) + ) + # self.validator.expect("size", str) + # self.validator.expect("size_text", str) + # self.validator.expect("description") + # self.validator.expect("underAge", bool) + # self.validator.expect("copyright", bool) + # self.validator.expect("copyright_att", str) + # self.validator.expect("guardian_info", str) + + self = expect_media_fields(self) args, err = self.validator.verify(args) if err: @@ -160,6 +170,7 @@ def update(self, request): .expect("location", "location", is_required=False) ) + self = expect_media_fields(self) args, err = self.validator.verify(args) if err: return err diff --git a/src/api/services/media_library.py b/src/api/services/media_library.py index 5d8fd0f3c..42831e767 100644 --- a/src/api/services/media_library.py +++ b/src/api/services/media_library.py @@ -14,10 +14,12 @@ def fetch_content(self, args): return self.organiseData(data=serialize_all(images, True), args=args), None def search(self, args, context): - images, error = self.store.search(args,context) + images, meta, error = self.store.search(args, context) if error: return None, error - return self.organiseData(data=serialize_all(images), args=args), None + organised = self.organiseData(data=serialize_all(images), args=args) + organised["meta"] = meta + return organised, None def organiseData(self, **kwargs): data = kwargs.get("data") or [] @@ -40,28 +42,30 @@ def organiseData(self, **kwargs): "images": data, } - def remove(self, args,context): - response, error = self.store.remove(args,context) + def remove(self, args, context): + response, error = self.store.remove(args, context) if error: return None, error return response, None - def addToGallery(self, args,context): - image, error = self.store.addToGallery(args,context) + def addToGallery(self, args, context): + image, error = self.store.addToGallery(args, context) if error: return None, error return image.simple_json(), None - - def edit_details(self, args,context): - media, error = self.store.edit_details(args,context) + + def edit_details(self, args, context): + media, error = self.store.edit_details(args, context) if error: return None, error - if media: - return self.getImageInfo({"media_id": media.id}) # Refer back to the getinfo routine so that data can be returned in the same structture + if media: + return self.getImageInfo( + {"media_id": media.id} + ) # Refer back to the getinfo routine so that data can be returned in the same structture return {}, None - - def find_images(self, args,context): - images, error = self.store.find_images(args,context) + + def find_images(self, args, context): + images, error = self.store.find_images(args, context) if error: return None, error images = serialize_all(images) @@ -79,7 +83,7 @@ def getImageInfo(self, args): events = serialize_all(media.events.all()) actions = serialize_all(media.actions.all()) testimonials = serialize_all(media.testimonials.all()) - vendors = serialize_all(media.vender_logo.all()) # yhup, thats right. lmfao! + vendors = serialize_all(media.vender_logo.all()) # yhup, thats right. lmfao! media_json = get_json_if_not_none(media, True) return { **media_json, @@ -88,6 +92,6 @@ def getImageInfo(self, args): "event": events, "action": actions, "testimonial": testimonials, - "vendor": vendors + "vendor": vendors, }, }, None diff --git a/src/api/store/action.py b/src/api/store/action.py index 90ce20fdd..671fd0eb7 100644 --- a/src/api/store/action.py +++ b/src/api/store/action.py @@ -1,6 +1,8 @@ from _main_.utils.footage.FootageConstants import FootageConstants from _main_.utils.footage.spy import Spy -from api.tests.common import RESET +from _main_.utils.utils import Console +from api.store.common import get_media_info, make_media_info +from api.tests.common import RESET, makeUserUpload from api.utils.api_utils import is_admin_of_community from api.utils.filter_functions import get_actions_filter_params from database.models import Action, UserProfile, Community, Media @@ -72,6 +74,7 @@ def create_action(self, context: Context, args, user_submitted) -> Tuple[dict, M calculator_action = args.pop('calculator_action', None) title = args.get('title', None) user_email = args.pop('user_email', context.user_email) + image_info = make_media_info(args) # check if there is an existing action with this name and community actions = Action.objects.filter(title=title, community__id=community_id, is_deleted=False) @@ -90,6 +93,9 @@ def create_action(self, context: Context, args, user_submitted) -> Tuple[dict, M if user_submitted: name = f'ImageFor {new_action.title} Action' media = Media.objects.create(name=name, file=images) + # create user media upload here + user_media_upload = makeUserUpload(media = media,info=image_info, communities=[community]) + else: media = Media.objects.filter(pk = images[0]).first() new_action.image = media @@ -104,6 +110,9 @@ def create_action(self, context: Context, args, user_submitted) -> Tuple[dict, M user = UserProfile.objects.filter(email=user_email).first() if user: new_action.user = user + if user_media_upload: + user_media_upload.user = user + user_media_upload.save() #save so you set an id new_action.save() @@ -192,6 +201,7 @@ def copy_action(self, context: Context, args) -> Tuple[Action, MassEnergizeAPIEr def update_action(self, context: Context, args, user_submitted) -> Tuple[dict, MassEnergizeAPIError]: try: + image_info = make_media_info(args) action_id = args.pop('action_id', None) actions = Action.objects.filter(id=action_id) if not actions: @@ -233,6 +243,14 @@ def update_action(self, context: Context, args, user_submitted) -> Tuple[dict, M actions.update(**args) action = actions.first() # refresh after update + + if community_id and not args.get('is_global', False): + community = Community.objects.filter(id=community_id).first() + if community: + action.community = community + else: + action.community = None + if image: #now, images will always come as an array of ids, or "reset" string if user_submitted: if "ImgToDel" in image: @@ -240,6 +258,7 @@ def update_action(self, context: Context, args, user_submitted) -> Tuple[dict, M else: image= Media.objects.create(file=image, name=f'ImageFor {action.title} Action') action.image = image + makeUserUpload(media = image,info=image_info, user = action.user, communities=[community]) else: if image[0] == RESET: #if image is reset, delete the existing image action.image = None @@ -247,6 +266,12 @@ def update_action(self, context: Context, args, user_submitted) -> Tuple[dict, M media = Media.objects.filter(id = image[0]).first() action.image = media + if action.image: + old_image_info, can_save_info = get_media_info(action.image) + if can_save_info: + action.image.user_upload.info.update({**old_image_info,**image_info}) + action.image.user_upload.save() + action.steps_to_take = steps_to_take action.deep_dive = deep_dive @@ -256,12 +281,7 @@ def update_action(self, context: Context, args, user_submitted) -> Tuple[dict, M if vendors: action.vendors.set(vendors) - if community_id and not args.get('is_global', False): - community = Community.objects.filter(id=community_id).first() - if community: - action.community = community - else: - action.community = None + if calculator_action: ccAction = CCAction.objects.filter(pk=calculator_action).first() diff --git a/src/api/store/common.py b/src/api/store/common.py index 0b0b48ba0..5cd4abd71 100644 --- a/src/api/store/common.py +++ b/src/api/store/common.py @@ -5,9 +5,9 @@ import pytz from _main_.utils.utils import Console from api.store.utils import getCarbonScoreFromActionRel -from database.models import UserActionRel +from database.models import UserActionRel from django.db.models import Q -from django.utils import timezone +from django.utils import timezone LAST_VISIT = "last-visit" @@ -15,12 +15,14 @@ LAST_MONTH = "last-month" LAST_YEAR = "last-year" -def js_datetime_to_python(datetext): + +def js_datetime_to_python(datetext): _format = "%Y-%m-%dT%H:%M:%SZ" _date = datetime.datetime.strptime(datetext, _format) return pytz.utc.localize(_date) -def make_time_range_from_text(time_range): + +def make_time_range_from_text(time_range): today = datetime.datetime.utcnow() if time_range == LAST_WEEK: start_time = today - datetime.timedelta(days=7) @@ -31,32 +33,36 @@ def make_time_range_from_text(time_range): end_time = today elif time_range == LAST_YEAR: start_time = today - datetime.timedelta(days=365) - end_time = today + end_time = today return [pytz.utc.localize(start_time), pytz.utc.localize(end_time)] + def count_action_completed_and_todos(**kwargs): """ ### args: communities(list), actions(list), time_range(str), start_date(str), end_date(str) This function counts how many times an action has been completed, or added to todolist Returns an array of dictionaries with the following: (name,id,done_count, todo_count, carbon_score, category) - * Given a list of communities, todo/done will be counted within only those communities. - * When given a list of actions, counts will only be done for only the actions given + * Given a list of communities, todo/done will be counted within only those communities. + * When given a list of actions, counts will only be done for only the actions given * When given both (actions & communities) an AND query will be built before counting * And when a time range is specified, all the query combinations listed above will run within the given time range """ - + communities = kwargs.get("communities", []) actions = kwargs.get("actions", []) action_count_objects = {} query = None - time_range = kwargs.get("time_range") + time_range = kwargs.get("time_range") # ---------------------------------------------------------------------------- - if time_range == "custom": - start_date = kwargs.get('start_date',"") - end_date = kwargs.get("end_date","") - time_range = [js_datetime_to_python(start_date), js_datetime_to_python(end_date)] - else: + if time_range == "custom": + start_date = kwargs.get("start_date", "") + end_date = kwargs.get("end_date", "") + time_range = [ + js_datetime_to_python(start_date), + js_datetime_to_python(end_date), + ] + else: time_range = make_time_range_from_text(time_range) if time_range else [] # ---------------------------------------------------------------------------- @@ -70,13 +76,13 @@ def count_action_completed_and_todos(**kwargs): action__in=actions, is_deleted=False, ) - # ---------------------------------------------------------------------------- + # ---------------------------------------------------------------------------- if not query: return [] # add time range specification to the query if available - if time_range: + if time_range: query &= Q(updated_at__range=time_range) completed_actions = UserActionRel.objects.filter(query).select_related( @@ -112,10 +118,6 @@ def count_action_completed_and_todos(**kwargs): return list(action_count_objects.values()) - - - - def create_pdf_from_rich_text(rich_text, filename): # Convert rich text to PDF pdf_buffer = io.BytesIO() @@ -125,17 +127,71 @@ def create_pdf_from_rich_text(rich_text, filename): # Close the buffer and return the response pdf_buffer.seek(0) - response = FileResponse(pdf_buffer, content_type='application/pdf') - response['Content-Disposition'] = f'attachment; filename={filename}.pdf' + response = FileResponse(pdf_buffer, content_type="application/pdf") + response["Content-Disposition"] = f"attachment; filename={filename}.pdf" return pdf_buffer.getvalue(), response -def sign_mou(mou_rich_text, user=None, date=None): - return f""" +def sign_mou(mou_rich_text, user=None, date=None): + return ( + f""" {mou_rich_text}

Signed By

Name: {user.full_name}

Date: {date}

- """ if (user and date) else mou_rich_text \ No newline at end of file + """ + if (user and date) + else mou_rich_text + ) + + +def expect_media_fields(self): + self.validator.expect("size", str).expect("size_text", str).expect( + "description" + ).expect("underAge", bool).expect("copyright", bool).expect( + "copyright_att", str + ).expect( + "guardian_info", str + ).expect( + "permission_key", str + ).expect( + "permission_notes", str + ) + return self + + +def make_media_info(args): + """Request arg names are different from how they are stored on the media object, so this function corrects the naming and makes sure it matches the structure that is actually saved on the media object""" + fields = [ + "copyright", + "copyright_att", + "underAge", + "size", + "size_text", + "guardian_info", + "permission_key", + "permission_notes" + ] + name_to_obj_name = { + "underAge": "has_children", + "copyright": "has_copyright_permission", + } + obj = {} + for name in fields: + value = args.pop(name, None) + name = name_to_obj_name.get(name, name) + if value: + obj[name] = value + return obj + + +def get_media_info(media): + """Retrieves media info from media object""" + if not media: + return {}, False + if hasattr(media, "user_upload"): + if hasattr(media.user_upload, "info"): + return media.user_upload.info or {}, True + return {}, False diff --git a/src/api/store/event.py b/src/api/store/event.py index ea5c7d153..87d51fb1c 100644 --- a/src/api/store/event.py +++ b/src/api/store/event.py @@ -1,7 +1,8 @@ from _main_.utils.footage.FootageConstants import FootageConstants from _main_.utils.footage.spy import Spy -from _main_.utils.utils import is_url_valid -from api.tests.common import RESET +from _main_.utils.utils import Console, is_url_valid +from api.store.common import get_media_info, make_media_info +from api.tests.common import RESET, makeUserUpload from api.utils.api_utils import is_admin_of_community from api.utils.filter_functions import get_events_filter_params from database.models import Event, RecurringEventException, UserProfile, EventAttendee, Media, Community @@ -242,6 +243,8 @@ def create_event(self, context: Context, args, user_submitted) -> Tuple[dict, Ma tags = args.pop('tags', []) community = args.pop("community_id", None) user_email = args.pop('user_email', context.user_email) + image_info = make_media_info(args) + start_date_and_time = args.get('start_date_and_time', None) end_date_and_time = args.get('end_date_and_time', None) @@ -306,12 +309,14 @@ def create_event(self, context: Context, args, user_submitted) -> Tuple[dict, Ma if user_submitted: name= f'ImageFor {new_event.name} Event' media = Media.objects.create(name=name, file=image) + user_media_upload = makeUserUpload(media = media,info=image_info,communities=[community]) else: media = Media.objects.filter(pk = image[0]).first() new_event.image = media if tags: new_event.tags.set(tags) + user = None if user_email: @@ -323,6 +328,9 @@ def create_event(self, context: Context, args, user_submitted) -> Tuple[dict, Ma user = UserProfile.objects.filter(email=user_email).first() if user: new_event.user = user + if user_media_upload: + user_media_upload.user = user + user_media_upload.save() if publicity_selections: new_event.communities_under_publicity.set(publicity_selections) @@ -359,6 +367,7 @@ def update_event(self, context: Context, args, user_submitted) -> Tuple[dict, Ma try: event_id = args.pop('event_id', None) events = Event.objects.filter(id=event_id) + image_info = make_media_info(args) publicity_selections = args.pop("publicity_selections", []) shared_to = args.pop("shared_to", []) @@ -477,6 +486,13 @@ def update_event(self, context: Context, args, user_submitted) -> Tuple[dict, Ma events.update(**args) event: Event = events.first() + if community_id: + community = Community.objects.filter(pk=community_id).first() + if community: + event.community = community + else: + event.community = None + if image: #now, images will always come as an array of ids, or "reset" string if user_submitted: if "ImgToDel" in image: @@ -484,19 +500,23 @@ def update_event(self, context: Context, args, user_submitted) -> Tuple[dict, Ma else: image= Media.objects.create(file=image, name=f'ImageFor {event.name} Event') event.image = image + makeUserUpload(media = image,info=image_info, user = event.user , communities=[community]) else: if image[0] == RESET: #if image is reset, delete the existing image event.image = None else: media = Media.objects.filter(id = image[0]).first() event.image = media + + if event.image: + old_image_info, can_save_info = get_media_info(event.image) + # There are media objects that do not have user upload references. (because we didnt have that model at the time of upload) thats why we need to check first + if can_save_info: + event.image.user_upload.info.update({**old_image_info,**image_info}) + event.image.user_upload.save() + + - if community_id: - community = Community.objects.filter(pk=community_id).first() - if community: - event.community = community - else: - event.community = None if tags: event.tags.set(tags) diff --git a/src/api/store/media_library.py b/src/api/store/media_library.py index f03115ccd..3f768b928 100644 --- a/src/api/store/media_library.py +++ b/src/api/store/media_library.py @@ -1,5 +1,8 @@ from functools import reduce from django.core.exceptions import ValidationError +from database.utils.settings.model_constants.user_media_uploads import ( + UserMediaConstants, +) from sentry_sdk import capture_message from _main_.utils.context import Context from _main_.utils.footage.FootageConstants import FootageConstants @@ -44,12 +47,15 @@ def edit_details(self, args, context: Context): copyright_att = args.get("copyright_att") tags = args.get("tags") communities = args.get("community_ids", []) + publicity = args.get("publicity", None) info = { **(user_media_upload.info or {}), "has_children": under_age, "has_copyright_permission": copyright_permission, "guardian_info": guardian_info, "copyright_att": copyright_att, + "permission_key": args.get("permission_key", None), + "permission_notes": args.get("permission_notes", None), } user_media_upload.info = info # user_media_upload.save() @@ -60,6 +66,8 @@ def edit_details(self, args, context: Context): user_media_upload.communities.clear() user_media_upload.communities.set(communities) + if publicity: + user_media_upload.publicity = publicity user_media_upload.save() if tags: @@ -196,6 +204,7 @@ def get_most_recent(self, args, context: Context): else: query |= qObj + count = Media.objects.filter(query).distinct().count() if not upper_limit and not lower_limit: images = Media.objects.filter(query).distinct().order_by("-id")[:limit] else: @@ -205,7 +214,26 @@ def get_most_recent(self, args, context: Context): .exclude(id__gte=lower_limit, id__lte=upper_limit) .order_by("-id")[:limit] ) - return images, None + return images, {"total": count}, None + + def get_public_images(self, args): + upper_limit = args.get("upper_limit") + lower_limit = args.get("lower_limit") + count = Media.objects.filter( + user_upload__publicity=UserMediaConstants.open() + ).count() + if not upper_limit and not lower_limit: + images = Media.objects.filter( + user_upload__publicity=UserMediaConstants.open() + ).order_by("-id")[:limit] + else: + images = ( + Media.objects.filter(user_upload__publicity=UserMediaConstants.open()) + .exclude(id__gte=lower_limit, id__lte=upper_limit) + .order_by("-id")[:limit] + ) + + return images, {"total": count}, None def search(self, args, context: Context): community_ids = args.get("target_communities", []) @@ -215,15 +243,19 @@ def search(self, args, context: Context): other_admins = not mine and other_admins search_by_community = not most_recent and community_ids keywords = args.get("keywords", []) + public = args.get("public", False) + + if public: + return self.get_public_images(args) if keywords: return self.get_by_keywords(args) if most_recent: if context.user_is_super_admin: return self.get_most_recent(args, context) - else : - communities,_ = get_admin_communities(context) + else: + communities, _ = get_admin_communities(context) args["target_communities"] = [c.id for c in communities] return self.get_most_recent(args, context) @@ -237,7 +269,7 @@ def search(self, args, context: Context): if other_admins: return self.get_uploads_by_user(args) - return [], None + return [], {}, None def get_by_keywords(self, args): words = args.get("keywords", []) @@ -255,6 +287,7 @@ def get_by_keywords(self, args): else: query |= queryObj + count = Media.objects.filter(query).distinct().count() if not upper_limit and not lower_limit: images = Media.objects.filter(query).distinct().order_by("-id")[:limit] else: @@ -265,13 +298,14 @@ def get_by_keywords(self, args): .order_by("-id")[:limit] ) - return images, None + return images, {"total": count}, None def get_uploads_by_user(self, args): user_ids = args.get("user_ids", []) upper_limit = args.get("upper_limit") lower_limit = args.get("lower_limit") query = Q(user_upload__user__id__in=user_ids) + count = Media.objects.filter(query).count() if upper_limit and lower_limit: images = ( Media.objects.filter(query) @@ -281,9 +315,7 @@ def get_uploads_by_user(self, args): else: images = Media.objects.filter(query).order_by("-id")[:limit] - return images, None - - + return images, {"total": count}, None def remove(self, args, context): media_id = args.get("media_id") @@ -325,6 +357,7 @@ def addToGallery(self, args, context): is_universal = args.get("is_universal", None) communities = user = None description = args.get("description", None) + publicity = args.get("publicity", None) # --------------------------------------------- copyright_permission = args.get("copyright", "") under_age = args.get("underAge", "") @@ -341,6 +374,8 @@ def addToGallery(self, args, context): "has_copyright_permission": copyright_permission, "guardian_info": guardian_info, "copyright_att": copyright_att, + "permission_key": args.get("permission_key", None), + "permission_notes": args.get("permission_notes", None), } try: @@ -361,6 +396,7 @@ def addToGallery(self, args, context): is_universal=is_universal, tags=tags, info=info, + publicity=publicity, ) # ---------------------------------------------------------------- Spy.create_media_footage( @@ -379,6 +415,9 @@ def makeMediaAndSave(self, **kwargs): user = kwargs.get("user") tags = kwargs.get("tags") info = kwargs.get("info") + publicity = kwargs.get("publicity", None) + if not publicity: + publicity = UserMediaConstants.open_to() communities = kwargs.get("communities") is_universal = kwargs.get("is_universal") is_universal = True if is_universal else False @@ -391,7 +430,11 @@ def makeMediaAndSave(self, **kwargs): file=file, ) user_media = UserMediaUpload( - user=user, media=media, is_universal=is_universal, info=info + user=user, + media=media, + is_universal=is_universal, + info=info, + publicity=publicity, ) user_media.save() if media: diff --git a/src/api/store/testimonial.py b/src/api/store/testimonial.py index 4c09c4e13..3fdc18024 100644 --- a/src/api/store/testimonial.py +++ b/src/api/store/testimonial.py @@ -1,6 +1,8 @@ from _main_.utils.footage.FootageConstants import FootageConstants from _main_.utils.footage.spy import Spy -from api.tests.common import RESET +from _main_.utils.utils import Console +from api.store.common import get_media_info, make_media_info +from api.tests.common import RESET, makeUserUpload from api.utils.api_utils import is_admin_of_community from api.utils.filter_functions import get_testimonials_filter_params from database.models import Testimonial, UserProfile, Media, Vendor, Action, Community, CommunityAdminGroup, Tag @@ -69,6 +71,7 @@ def list_testimonials(self, context: Context, args) -> Tuple[list, MassEnergizeA def create_testimonial(self, context: Context, args) -> Tuple[dict, MassEnergizeAPIError]: try: + image_info = make_media_info(args) images = args.pop("image", None) tags = args.pop('tags', []) action = args.pop('action', None) @@ -93,17 +96,7 @@ def create_testimonial(self, context: Context, args) -> Tuple[dict, MassEnergize new_testimonial.user = user - if images: - if type(images) == list: - # from admin portal, using media library - image = Media.objects.filter(id = images[0]).first(); - new_testimonial.image = image - else: - # from community portal, image upload - images.name = unique_media_filename(images) - - image = Media.objects.create(file=images, name=f"ImageFor {args.get('title', '')} Testimonial") - new_testimonial.image = image + if action: @@ -120,6 +113,21 @@ def create_testimonial(self, context: Context, args) -> Tuple[dict, MassEnergize else: testimonial_community = None + if images: + if type(images) == list: + # from admin portal, using media library + image = Media.objects.filter(id = images[0]).first(); + new_testimonial.image = image + else: + # from community portal, image upload + images.name = unique_media_filename(images) + image = Media.objects.create(file=images, name=f"ImageFor {args.get('title', '')} Testimonial") + new_testimonial.image = image + + user_media_upload = makeUserUpload(media = image,info=image_info, user = user,communities=[testimonial_community]) + user_media_upload.user = user + user_media_upload.save() + tags_to_set = [] for t in tags: tag = Tag.objects.filter(pk=t).first() @@ -142,6 +150,7 @@ def create_testimonial(self, context: Context, args) -> Tuple[dict, MassEnergize def update_testimonial(self, context: Context, args) -> Tuple[dict, MassEnergizeAPIError]: try: + image_info = make_media_info(args) id = args.pop("id", None) testimonials = Testimonial.objects.filter(id=id) if not testimonials: @@ -178,6 +187,13 @@ def update_testimonial(self, context: Context, args) -> Tuple[dict, MassEnergize testimonials.update(**args) testimonial = testimonials.first() # refresh after update + if community: + testimonial_community = Community.objects.filter(id=community).first() + if testimonial_community: + testimonial.community = testimonial_community + else: + testimonial.community = None + if images: if type(images) == list: if images[0] == RESET: @@ -191,6 +207,13 @@ def update_testimonial(self, context: Context, args) -> Tuple[dict, MassEnergize else: image = Media.objects.create(file=images, name=f"ImageFor {testimonial.title} Testimonial") testimonial.image = image + makeUserUpload(media = image,info=image_info, user = testimonial.user,communities=[testimonial_community]) + + if testimonial.image: + old_image_info, can_save_info = get_media_info(testimonial.image) + if can_save_info: + testimonial.image.user_upload.info.update({**old_image_info,**image_info}) + testimonial.image.user_upload.save() if action: testimonial_action = Action.objects.filter(id=action).first() @@ -204,12 +227,7 @@ def update_testimonial(self, context: Context, args) -> Tuple[dict, MassEnergize else: testimonial.vendor = None - if community: - testimonial_community = Community.objects.filter(id=community).first() - if testimonial_community: - testimonial.community = testimonial_community - else: - testimonial.community = None + if rank: testimonial.rank = rank diff --git a/src/api/store/vendor.py b/src/api/store/vendor.py index 3dff5da7f..a387ac521 100644 --- a/src/api/store/vendor.py +++ b/src/api/store/vendor.py @@ -1,6 +1,7 @@ from _main_.utils.footage.FootageConstants import FootageConstants from _main_.utils.footage.spy import Spy -from api.tests.common import RESET +from api.store.common import get_media_info, make_media_info +from api.tests.common import RESET, makeUserUpload from api.utils.filter_functions import get_vendor_filter_params from database.models import Vendor, UserProfile, Media, Community from _main_.utils.massenergize_errors import MassEnergizeAPIError, NotAuthorizedError, InvalidResourceError, CustomMassenergizeError @@ -63,6 +64,7 @@ def list_vendors(self, context: Context, args) -> Tuple[list, MassEnergizeAPIErr def create_vendor(self, context: Context, args, user_submitted) -> Tuple[Vendor, MassEnergizeAPIError]: try: + image_info = make_media_info(args) tags = args.pop('tags', []) communities = args.pop('communities', []) images = args.pop('image', None) @@ -81,10 +83,16 @@ def create_vendor(self, context: Context, args, user_submitted) -> Tuple[Vendor, args['location'] = None new_vendor = Vendor.objects.create(**args) + + if communities: + new_vendor.communities.set(communities) + if images: if user_submitted: name=f"ImageFor {new_vendor.name} Vendor" logo = Media.objects.create(name=name, file=images) + user_media_upload = makeUserUpload(media = logo,info=image_info,communities=new_vendor.communities) + else: logo = Media.objects.filter(pk = images[0]).first() new_vendor.logo = logo @@ -104,14 +112,16 @@ def create_vendor(self, context: Context, args, user_submitted) -> Tuple[Vendor, user = UserProfile.objects.filter(email=user_email).first() if user: new_vendor.user = user + if user_media_upload: + user_media_upload.user = user + user_media_upload.save() if website: new_vendor.more_info = {'website': website} new_vendor.save() - if communities: - new_vendor.communities.set(communities) + if tags: new_vendor.tags.set(tags) @@ -128,6 +138,7 @@ def create_vendor(self, context: Context, args, user_submitted) -> Tuple[Vendor, def update_vendor(self, context: Context, args, user_submitted) -> Tuple[dict, MassEnergizeAPIError]: try: + image_info = make_media_info(args) vendor_id = args.pop('vendor_id', None) vendors = Vendor.objects.filter(id=vendor_id) if not vendors: @@ -176,12 +187,20 @@ def update_vendor(self, context: Context, args, user_submitted) -> Tuple[dict, M else: image= Media.objects.create(file=images, name=f'ImageFor {vendor.name} Vendor') vendor.logo = image + makeUserUpload(media = image,info=image_info, user=vendor.user,communities=vendor.communities) + else: if images[0] == RESET: #if image is reset, delete the existing image vendor.logo = None else: media = Media.objects.filter(id = image[0]).first() vendor.logo = media + + if vendor.image: + old_image_info, can_save_info = get_media_info(vendor.logo) + if can_save_info: + vendor.image.user_upload.info.update({**old_image_info,**image_info}) + vendor.image.user_upload.save() if onboarding_contact_email: diff --git a/src/database/migrations/0140_usermediaupload_publicity.py b/src/database/migrations/0140_usermediaupload_publicity.py new file mode 100644 index 000000000..a6dda6776 --- /dev/null +++ b/src/database/migrations/0140_usermediaupload_publicity.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.14 on 2023-10-09 06:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('database', '0139_merge_20230921_1405'), + ] + + operations = [ + migrations.AddField( + model_name='usermediaupload', + name='publicity', + field=models.CharField(default='OPEN_TO', max_length=100), + ), + ] diff --git a/src/database/models.py b/src/database/models.py index 146993cbf..4a6806bf4 100644 --- a/src/database/models.py +++ b/src/database/models.py @@ -10,6 +10,9 @@ from _main_.utils.footage.FootageConstants import FootageConstants from database.utils.constants import * from database.utils.settings.admin_settings import AdminPortalSettings +from database.utils.settings.model_constants.user_media_uploads import ( + UserMediaConstants, +) from database.utils.settings.user_settings import UserPortalSettings from django.utils import timezone from django.core.files.storage import default_storage @@ -31,6 +34,7 @@ # ------------------------------------------------------------------------- + def get_enabled_flags( _self, users=False ): # _self : CommunityObject or UserProfileObject @@ -75,16 +79,21 @@ def user_is_due_for_mou(user): ).latest("signed_at") except PolicyAcceptanceRecords.DoesNotExist: return True, None - + # ok if user signed MOU after the date one year ago - if last_record.signed_at and last_record.signed_at > a_year_ago: + if last_record.signed_at and last_record.signed_at > a_year_ago: return False, last_record.simple_json() - + return True, last_record.simple_json() - -def fetch_few_visits(user): - footages = Footage.objects.filter(actor__id = user.id, activity_type=FootageConstants.sign_in(), portal = FootageConstants.on_user_portal()).values_list("created_at", flat=True)[:5] - if len(footages): + + +def fetch_few_visits(user): + footages = Footage.objects.filter( + actor__id=user.id, + activity_type=FootageConstants.sign_in(), + portal=FootageConstants.on_user_portal(), + ).values_list("created_at", flat=True)[:5] + if len(footages): return list(footages) visits = user.visit_log or [] @@ -265,16 +274,16 @@ def __str__(self): return str(self.id) + "-" + self.name + "(" + self.file.name + ")" def simple_json(self): - obj= { + obj = { "id": self.id, "name": self.name, - "url": self.file.url, + "url": self.file.url, } - if hasattr(self, "user_upload"): obj["created_at"] = self.user_upload.created_at + obj["info"] = self.user_upload.info - return obj + return obj def full_json(self): return { @@ -285,8 +294,8 @@ def full_json(self): "media_type": self.media_type, "tags": [tag.simple_json() for tag in self.tags.all()], } - - def delete(self, *args, **kwargs): + + def delete(self, *args, **kwargs): # Overriding the default delete fxn to delete actual file from storage as well if self.file: file_path = self.file.name @@ -468,8 +477,12 @@ class Community(models.Model): subdomain = models.SlugField(max_length=SHORT_STR_LEN, unique=True, db_index=True) owner_name = models.CharField(max_length=SHORT_STR_LEN, default="Unknown") owner_email = models.EmailField(blank=False) - contact_sender_alias = models.CharField(blank=True, null=True, max_length=SHORT_STR_LEN) - owner_phone_number = models.CharField(blank=True, null=True, max_length=SHORT_STR_LEN) + contact_sender_alias = models.CharField( + blank=True, null=True, max_length=SHORT_STR_LEN + ) + owner_phone_number = models.CharField( + blank=True, null=True, max_length=SHORT_STR_LEN + ) about_community = models.TextField(max_length=LONG_STR_LEN, blank=True) logo = models.ForeignKey( Media, @@ -602,43 +615,47 @@ def full_json(self): carbon_footprint_reduction = 0 for actionRel in done_actions: if actionRel.action and actionRel.action.calculator_action: - carbon_footprint_reduction += AverageImpact(actionRel.action.calculator_action, actionRel.date_completed) + carbon_footprint_reduction += AverageImpact( + actionRel.action.calculator_action, actionRel.date_completed + ) goal["organic_attained_carbon_footprint_reduction"] = carbon_footprint_reduction # calculate values for community impact to be displayed on front-end sites - impact_page_settings: ImpactPageSettings = ImpactPageSettings.objects.filter(community__id=self.pk).first() + impact_page_settings: ImpactPageSettings = ImpactPageSettings.objects.filter( + community__id=self.pk + ).first() if impact_page_settings: display_prefs = impact_page_settings.more_info or {} else: - #capture_message("Impact Page Settings not found", level="error") - display_prefs = {} # not usual - show nothing - + # capture_message("Impact Page Settings not found", level="error") + display_prefs = {} # not usual - show nothing + value = 0 if display_prefs.get("manual_households"): - value += goal.get("initial_number_of_households",0) + value += goal.get("initial_number_of_households", 0) if display_prefs.get("state_households"): - value += goal.get("attained_number_of_households",0) + value += goal.get("attained_number_of_households", 0) if display_prefs.get("platform_households"): - value += goal.get("organic_attained_number_of_households",0) + value += goal.get("organic_attained_number_of_households", 0) goal["displayed_number_of_households"] = value value = 0 if display_prefs.get("manual_actions"): - value += goal.get("initial_number_of_actions",0) + value += goal.get("initial_number_of_actions", 0) if display_prefs.get("state_actions"): - value += goal.get("attained_number_of_actions",0) + value += goal.get("attained_number_of_actions", 0) if display_prefs.get("platform_actions"): - value += goal.get("organic_attained_number_of_actions",0) + value += goal.get("organic_attained_number_of_actions", 0) goal["displayed_number_of_actions"] = value value = 0 if display_prefs.get("manual_carbon"): - value += goal.get("initial_carbon_footprint_reduction",0) + value += goal.get("initial_carbon_footprint_reduction", 0) if display_prefs.get("state_carbon"): - value += goal.get("attained_carbon_footprint_reduction",0) + value += goal.get("attained_carbon_footprint_reduction", 0) if display_prefs.get("platform_carbon"): - value += goal.get("organic_attained_carbon_footprint_reduction",0) + value += goal.get("organic_attained_carbon_footprint_reduction", 0) goal["displayed_carbon_footprint_reduction"] = value locations = "" @@ -702,7 +719,7 @@ def full_json(self): "locations": locations, "feature_flags": get_enabled_flags(self), "is_demo": self.is_demo, - "contact_sender_alias": self.contact_sender_alias + "contact_sender_alias": self.contact_sender_alias, } class Meta: @@ -1092,10 +1109,10 @@ def full_json(self): "admin_portal_settings": admin_portal_settings, } data["feature_flags"] = get_enabled_flags(self, True) - if self.is_community_admin: + if self.is_community_admin: mou_details = user_is_due_for_mou(self) data["needs_to_accept_mou"] = mou_details[0] - data["mou_details"] = mou_details[1] + data["mou_details"] = mou_details[1] return data @@ -1135,15 +1152,13 @@ class PolicyAcceptanceRecords(models.Model): created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) - def simple_json(self): - res = model_to_dict( - self, [ "signed_at", "id"] - ) - if self.policy: + def simple_json(self): + res = model_to_dict(self, ["signed_at", "id"]) + if self.policy: res["policy"] = self.policy.simple_json() return res - def full_json(self): + def full_json(self): return self.simple_json() def __str__(self) -> str: @@ -1156,7 +1171,32 @@ class Meta: class UserMediaUpload(models.Model): - """A class that creates a relationship between a user(all user kinds) on the platform and media they have uploaded""" + """A class that creates a relationship between a user(all user kinds) on the platform and media they have uploaded + + Attributes + ---------- + user : UserProfile + A user profile object of the currently signed in user who uploaded the media + + communities: Community + All communities that have access to the attached media object + + media : Media + A reference to the actual media object + + is_universal: bool + True/False value that indicates whether or not an image is open to everyone. + PS: Its no longer being used (as at 12/10/23). We want more than two states, so we now use "publicity" + + publicity: str + This value is used to determine whether or not an upload is OPEN_TO specific communities, CLOSED_TO, or wide open to any communities check UserMediaConstants for all the available options + + info: JSON + Json field that stores very important information about the attached media. Example: has_copyright_permission,copyright_att,guardian_info,size etc. + + settings: JSON + Just another field to store more information about the media (I dont think we use this...) + """ id = models.AutoField(primary_key=True) user = models.ForeignKey( @@ -1182,13 +1222,22 @@ class UserMediaUpload(models.Model): info = models.JSONField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) + publicity = models.CharField( + max_length=SHORT_STR_LEN, + default=UserMediaConstants.open_to(), + null=True, + blank=True, + ) def __str__(self): - return f"{str(self.id)} - {self.media.name} from {self.user.preferred_name or self.user.full_name} " - + if self.user: + return f"{str(self.id)} - {self.media.name} from {self.user.preferred_name or self.user.full_name} " + + return f"{str(self.id)} - {self.media.name} from ..." + def simple_json(self): res = model_to_dict( - self, ["settings", "media", "created_at", "id", "is_universal", "info"] + self, ["settings", "media", "created_at", "id", "is_universal", "info", "publicity"] ) res["user"] = get_summary_info(self.user) res["image"] = get_json_if_not_none(self.media) @@ -1925,8 +1974,10 @@ def simple_json(self): # Adding this so that vendors will be preselected when creating/updating action. # List of vendors will typically not be that long, so this doesnt pose any problems data["vendors"] = [v.info() for v in self.vendors.all()] - data["action_users"] =len( UserActionRel.objects.filter(action=self, is_deleted=False)) or 0 - + data["action_users"] = ( + len(UserActionRel.objects.filter(action=self, is_deleted=False)) or 0 + ) + if self.user: data["user_email"] = self.user.email return data @@ -1945,13 +1996,14 @@ def full_json(self): "email": u.user.email, "full_name": u.user.full_name, "real_estate_unit": { - "zipcode":u.real_estate_unit.address.zipcode if u.real_estate_unit and u.real_estate_unit.address else None, + "zipcode": u.real_estate_unit.address.zipcode + if u.real_estate_unit and u.real_estate_unit.address + else None, "name": u.real_estate_unit.name if u.real_estate_unit else None, }, "date_completed": u.date_completed, "carbon_impact": AverageImpact(u.action.calculator_action, u.date_completed) if u.action.calculator_action else None, "recorded_at": u.updated_at, - } for u in UserActionRel.objects.filter(action=self, is_deleted=False) ] or [] @@ -3706,19 +3758,23 @@ def full_json(self): def enabled(self): current_date_and_time = datetime.datetime.now(timezone.utc) - if self.expires_on and self.expires_on str: class Meta: db_table = "footages" ordering = ("-id",) - diff --git a/src/database/utils/settings/model_constants/user_media_uploads.py b/src/database/utils/settings/model_constants/user_media_uploads.py new file mode 100644 index 000000000..c081f4bd2 --- /dev/null +++ b/src/database/utils/settings/model_constants/user_media_uploads.py @@ -0,0 +1,7 @@ + + +from database.utils.settings.model_constants.events import EventConstants + + +class UserMediaConstants(EventConstants): + pass