diff --git a/biz/admin.py b/biz/admin.py index e1a4556..a83003e 100644 --- a/biz/admin.py +++ b/biz/admin.py @@ -429,57 +429,3 @@ class ActivityAdmin(SyncedObjectAdmin): def get_event_display(self, obj): return str(obj) get_event_display.short_description = 'Activity' - -# @admin.register(EmailCampaign) -# class EmailCampaignAdmin(admin.ModelAdmin): -# list_display = ('subject', 'event', 'sent_at') -# list_filter = ('sent_at',) -# search_fields = ('subject', 'content') -# date_hierarchy = 'sent_at' -# readonly_fields = ('sent_at',) - -# @admin.register(EmailTracker) -# class EmailTrackerAdmin(admin.ModelAdmin): -# list_display = ( -# 'campaign', -# 'prospect', -# 'tracking_id', -# 'sent_status', -# 'opened_status', -# 'clicked_status' -# ) -# list_filter = ('sent', 'opened', 'clicked') -# search_fields = ( -# 'prospect__name', -# 'prospect__email', -# 'campaign__subject' -# ) -# readonly_fields = ( -# 'tracking_id', 'sent', 'sent_at', -# 'opened', 'opened_at', -# 'clicked', 'clicked_at' -# ) -# date_hierarchy = 'sent_at' - -# def sent_status(self, obj): -# return self._get_status_html(obj.sent, obj.sent_at) -# sent_status.short_description = 'Sent' -# sent_status.allow_tags = True - -# def opened_status(self, obj): -# return self._get_status_html(obj.opened, obj.opened_at) -# opened_status.short_description = 'Opened' -# opened_status.allow_tags = True - -# def clicked_status(self, obj): -# return self._get_status_html(obj.clicked, obj.clicked_at) -# clicked_status.short_description = 'Clicked' -# clicked_status.allow_tags = True - -# def _get_status_html(self, status, date): -# if status: -# return format_html( -# ' {}', -# date.strftime('%Y-%m-%d %H:%M') if date else '' -# ) -# return format_html('') diff --git a/sync/README.md b/sync/README.md new file mode 100644 index 0000000..b309631 --- /dev/null +++ b/sync/README.md @@ -0,0 +1,21 @@ +### Synchronization quick ReadMe + +- Data class must extend BaseModel +- Admin classes must extend SyncedObjectAdmin to have updates saved in the BaseModel properties +- The SynchronizationApi defines a get and a post service to POST new data, and GET updates. When performing an operation on a data, a ModelLog instance is created with the related information. When performing a GET, we retrieve the list of ModelLogs to sent the new data to the user. +- routing.py defines the URL of the websocket where messages are sent when updates are made. URL is by user. + + +### Sharing + +- Data can be shared between users. To do that, a new DataAccess object can be created to define the owner, the authorized user, and the object id. +- By default, the whole hierarchy of objects are shared, from the data parents to all its children. +- Special data path can be specified for a class by defining a setting + +example: +SYNC_MODEL_CHILDREN_SHARING = { + 'Match': ['team_scores', 'team_registration', 'player_registrations'] +} +Here when sharing a Match, we also share objects accessed through the names of the properties to get TeamScore, TeamRegistration and PlayerRegistration. + +- It's also possible to exclude a class from being sharable by setting sharable = False in its definition. In PadelClub, Club is the top entity that links all data together, so we don't want the automatic data scanning to share clubs. diff --git a/sync/admin.py b/sync/admin.py index 0f9f713..296df11 100644 --- a/sync/admin.py +++ b/sync/admin.py @@ -5,7 +5,7 @@ from .models import BaseModel, ModelLog, DataAccess class SyncedObjectAdmin(admin.ModelAdmin): - exclude = ('data_access_ids',) + # exclude = ('data_access_ids',) raw_id_fields = ['related_user', 'last_updated_by'] def save_model(self, request, obj, form, change): diff --git a/sync/models/base.py b/sync/models/base.py index 562dd56..564def2 100644 --- a/sync/models/base.py +++ b/sync/models/base.py @@ -16,6 +16,8 @@ class BaseModel(models.Model): last_updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL, related_name='+') data_access_ids = models.JSONField(default=list) + sharable = True + class Meta: abstract = True @@ -39,6 +41,8 @@ class BaseModel(models.Model): } def update_data_access_list(self): + if self.sharable == False: + return related_instances = self.sharing_related_instances() data_access_ids = set() for instance in related_instances: @@ -46,21 +50,18 @@ class BaseModel(models.Model): data_access_ids.update(instance.data_access_ids) # print(f'related_instances = {related_instances}') - # data_access_ids = [instance.data_access_ids for instance in related_instances if isinstance(instance, BaseModel)] - # data_access_ids.extend(self.data_access_ids) self.data_access_ids = list(data_access_ids) - # DataAccess = apps.get_model('sync', 'DataAccess') - # data_accesses = DataAccess.objects.filter(model_id__in=related_ids) - # for data_access in data_accesses: - # self.add_data_access_relation(data_access) - def add_data_access_relation(self, data_access): + if self.sharable == False: + return str_id = str(data_access.id) if str_id not in self.data_access_ids: self.data_access_ids.append(str_id) def remove_data_access_relation(self, data_access): + if self.sharable == False: + return try: self.data_access_ids.remove(str(data_access.id)) except ValueError: diff --git a/tournaments/admin.py b/tournaments/admin.py index 4febf0d..b8fd869 100644 --- a/tournaments/admin.py +++ b/tournaments/admin.py @@ -95,7 +95,7 @@ class EventAdmin(SyncedObjectAdmin): readonly_fields = ['display_images_preview'] fieldsets = [ - (None, {'fields': ['last_update', 'related_user', 'name', 'club', 'creator', 'creation_date', 'tenup_id']}), + (None, {'fields': ['data_access_ids','last_update', 'related_user', 'name', 'club', 'creator', 'creation_date', 'tenup_id']}), ('Images', {'fields': ['display_images_preview'], 'classes': ['collapse']}), ] diff --git a/tournaments/models/club.py b/tournaments/models/club.py index d0f6b56..3582f51 100644 --- a/tournaments/models/club.py +++ b/tournaments/models/club.py @@ -27,6 +27,8 @@ class Club(BaseModel): broadcast_code = models.CharField(max_length=10, null=True, blank=True, unique=True) admin_visible = models.BooleanField(default=False) + sharable = False + def delete_dependencies(self): for court in self.courts.all(): # court.delete_dependencies()