Skip to content

Commit

Permalink
Merge pull request #33 from xjlin0/1.0rc4
Browse files Browse the repository at this point in the history
1.0rc4 release
  • Loading branch information
xjlin0 authored Oct 16, 2023
2 parents d9973a7 + 80799cc commit ad4b7c2
Show file tree
Hide file tree
Showing 68 changed files with 3,334 additions and 1,228 deletions.
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ export DJANGO_SECRET_KEY=<<production Django secret key>>
* if using proxy on apache, please set `ProxyPreserveHost on` to pass host header in requests, so that the email links will have the correct domain name.
* For keeping the site surviving reboot, add starting of the Django upon system reboot, such as `sudo su user_name sh -c 'sleep 99 && cd ~user_name/repo_dir && docker-compose -f production.yml up -d' >> /tmp/attendee_startup.log` (need work)
</details>
* enter Redis-CLI by `docker exec -it redis redis-cli`

## DB SQL Backup & Restore process (with production.yml)
<details>
Expand Down Expand Up @@ -293,7 +294,7 @@ DJANGO_SECRET_KEY=your_django_secret_key
docker-compose -f local.yml run django python manage.py dumpdata -e users.user -e admin.logentry -e sessions.session -e contenttypes.contenttype -e sites.site -e account.emailaddress -e account.emailconfirmation -e socialaccount.socialtoken -e auth.permission -e pghistory.context -e pghistory.aggregateevent -e users.userhistory -e users.menushistory -e users.menuauthgroupshistory -e users.groupshistory -e users.grouppermissionshistory -e users.usergroupshistory -e users.userpermissionshistory -e users.emailaddresshistory -e users.emailconfirmationhistory -e whereabouts.organizationshistory -e whereabouts.divisionshistory -e whereabouts.placeshistory -e whereabouts.campuseshistory -e whereabouts.propertieshistory -e whereabouts.suiteshistory -e whereabouts.roomshistory -e whereabouts.countryhistory -e whereabouts.statehistory -e whereabouts.localityhistory -e whereabouts.addresshistory -e persons.categorieshistory -e persons.noteshistory -e persons.pastshistory -e persons.folkshistory -e persons.attendeeshistory -e persons.folkattendeeshistory -e persons.relationshistory -e persons.registrationshistory -e persons.attendingshistory -e persons.attendingmeetshistory -e occasions.assemblieshistory -e occasions.attendanceshistory -e occasions.charactershistory -e occasions.gatheringshistory -e occasions.meetshistory -e occasions.messagetemplateshistory -e occasions.priceshistory -e occasions.teamshistory -e occasions.calendarhistory -e occasions.calendarrelationhistory -e occasions.eventhistory -e occasions.eventrelationhistory -e occasions.occurrencehistory -e occasions.rulehistory -e occasions.periodictaskhistory -e occasions.crontabschedulehistory -e occasions.intervalschedulehistory -e users.permissionshistory -e users.GroupPermissionProxy -e users.UserGroupProxy -e users.UserPermissionProxy --indent 2 > fixtures/db_seed2.json
```
* go to Django admin to add the first organization and all groups to the first user (superuser) at http://<<your domain name>>:8008/admin/users/user/
* to see django log: `docker-compose -f production.yml logs django`
* to see django log: `docker-compose -f local.yml logs django`
</details>

## [How to start dev env on Windows](https://cookiecutter-django.readthedocs.io/en/latest/developing-locally-docker.html)
Expand Down Expand Up @@ -326,6 +327,7 @@ Please add your IP to ALLOWED_HOSTS in config/settings/local.py
* Enter Django console by `docker-compose -f local.yml run django python manage.py shell_plus`
* remote debug in PyCharm for docker, please check [django cookie doc](https://github.com/pydanny/cookiecutter-django/blob/master/{{cookiecutter.project_slug}}/docs/pycharm/configuration.rst).
</details>
* enter Redis-CLI by `docker exec -it redis redis-cli`

## How to start dev env on macOS with VirtualBox and docker-machine
<details>
Expand Down Expand Up @@ -370,16 +372,23 @@ All libraries are included to facilitate offline development, it will take port
* check local python version, Django coockie cutter is developed with Python 3
* Install pre-commit for python, such as `pip3 install pre-commit` (pre-commit settings are at .git/hooks/pre-commit).
* There is no need to have local docker machine, Django or Postgres running.
* Install and start [docker desktop](https://www.docker.com/products/docker-desktop) (including docker compose), and [add local repo directory to file sharing in docker desktop preference](https://docs.docker.com/desktop/mac/#file-sharing).
* Add .envs/.local/.sendgrid.env
```commandline
SENDGRID_API_KEY=<<your sendgrid api key>>
DJANGO_DEFAULT_FROM_EMAIL=<<your email>>
EMAIL_HOST=sendgrid
```
* Install and start [docker desktop](https://www.docker.com/products/docker-desktop) (including docker compose), and [add local repo directory to file sharing in docker desktop preference](https://docs.docker.com/desktop/settings/mac/#file-sharing).
* build and start the CentOS based local machine by `docker-compose -f local.yml build && docker-compose -f local.yml up -d`, your site will be at http://0.0.0.0:8008/
* to see django log: `docker-compose -f local.yml logs django`
* enter Redis-CLI by `docker exec -it redis redis-cli`


## DB SQL Backup & Restore process (with local.yml)
* backup current db to container `docker-compose -f local.yml exec postgres backup`
* list backup files in container `docker-compose -f local.yml exec postgres backups`
* copy all backup files from container to dev local computer `docker cp $(docker-compose -f local.yml ps -q postgres):/backups ./backups`
* copy all backup files from dev local computer to container `docker cp ./backups/* $(docker-compose -f local.yml ps -q postgres):/backups/`
* copy a backup file from dev local computer to container `docker cp ./backups/<filename> $(docker-compose -f local.yml ps -q postgres):/backups/`
* restore a backup from a backup file in container `docker-compose -f local.yml exec postgres restore backup_2018_03_13T09_05_07.sql.gz`
* print INSERT commands for a table `docker-compose -f local.yml exec postgres pg_dump --column-inserts --data-only --table=<<table name>> -d attendees --username=<<POSTGRES_USER in .envs/.local/.postgres>>`

Expand Down
3 changes: 1 addition & 2 deletions attendees/context_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from datetime import datetime
from django.conf import settings
from urllib import parse
import json
from attendees.users.models import Menu


Expand Down Expand Up @@ -32,7 +31,7 @@ def common_variables(request): # TODO move organization info to view
'timezone_name': datetime.now(timezone(parse.unquote(tzname))).tzname(),
'user_organization_name': user_organization_name,
'user_organization_name_slug': user_organization_name_slug,
'user_api_allowed_url_name': json.dumps({name: True for name in request.user.allowed_url_names()} if hasattr(request.user, 'allowed_url_names') else {}),
'user_api_allowed_url_name': {name: True for name in request.user.allowed_url_names()} if hasattr(request.user, 'allowed_url_names') else {},
'user_attendee_id': user_attendee_id,
'main_menus': main_menus,
}
2 changes: 1 addition & 1 deletion attendees/occasions/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ class AssemblyAdmin(PgHistoryPage, admin.ModelAdmin):
prepopulated_fields = {"slug": ("display_name",)}
# inlines = (AssemblyContactInline,)
list_display_links = ("display_name",)
list_display = ("id", "division", "display_name", "slug", "get_addresses")
list_display = ("id", "division", "display_name", "display_order", "slug", "get_addresses")
readonly_fields = ["id", "created", "modified"]

def formfield_for_foreignkey(self, db_field, request, **kwargs):
Expand Down
2 changes: 2 additions & 0 deletions attendees/occasions/serializers/attendance_etc_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class AttendanceEtcSerializer(serializers.ModelSerializer):
registrant_attendee_id = serializers.CharField(read_only=True)
attending__attendee__infos__names__original = serializers.CharField(read_only=True, source='attending_name')
photo = serializers.CharField(read_only=True)
attending__attendee__infos__fixed__grade = serializers.CharField(read_only=True)
attending__attendee__infos__fixed__food_pref = serializers.CharField(read_only=True)
# file = serializers.FileField(use_url=True, allow_empty_file=True, allow_null=True) # cause 400 or with local domain name
file_path = serializers.SerializerMethodField(required=False, read_only=True)
encoded_file = serializers.CharField(required=False)
Expand Down
2 changes: 2 additions & 0 deletions attendees/occasions/services/attendance_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ def by_organization_meet_characters(current_user, meet_slugs, character_slugs, s
),
output_field=CharField()
)
annotations['attending__attendee__infos__fixed__grade'] = F("attending__attendee__infos__fixed__grade")
annotations['attending__attendee__infos__fixed__food_pref'] = F("attending__attendee__infos__fixed__food_pref")
else:
if not attendee:
annotations['attending__attendee__first_name'] = F("attending__attendee__first_name")
Expand Down
9 changes: 5 additions & 4 deletions attendees/occasions/views/api/organization_meets.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
class OrganizationMeetsViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows all/grouped Meet in current user's organization filtered by date to be viewed or edited.
ps. Generally speaking meets canNOT be filtered by start/finish unless specified sinces user may want to see future/past ones.
Todo 20210711 only coworkers/organizers can see all Meets, general users should only see what they attended
Todo 20210815 if limiting by meet's shown_audience, non-coworker assigned to non-public meets won't show
"""
Expand Down Expand Up @@ -63,17 +64,17 @@ def get_queryset(self):
extra_filter.add(Q(**terms), Q.AND)

if start:
extra_filter.add((Q(finish__isnull=True) | Q(finish__gte=start)), Q.AND)
extra_filter.add(Q(finish__gte=start), Q.AND)

if finish:
extra_filter.add((Q(start__isnull=True) | Q(start__lte=finish)), Q.AND)
extra_filter.add(Q(start__lte=finish), Q.AND)

return (
Meet.objects.filter(extra_filter)
Meet.objects.select_related('assembly').filter(extra_filter)
.annotate(
assembly_name=F("assembly__display_name"),
)
.order_by('assembly_name', 'display_name')
.order_by('assembly__display_order', 'assembly_name', 'display_name')
)

else:
Expand Down
1 change: 1 addition & 0 deletions attendees/occasions/views/api/user_assembly_meets.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def get_queryset(self):
"""
Todo: this endpoint is used by datagrid_attendee_update_view page (with params).
Do check if the editor and the editing target relations and permissions
ps. Generally speaking meets canNOT be filtered by start/finish since users may want to see future/past ones.
:return:
"""
current_user = self.request.user
Expand Down
2 changes: 1 addition & 1 deletion attendees/occasions/views/page/roster_list_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def get_context_data(self, **kwargs):
"user_can_write": MenuService.is_user_allowed_to_write(self.request),
# "content_type_models_endpoint": "/whereabouts/api/content_type_models/",
"series_gatherings_endpoint": "/occasions/api/series_gatherings/",
# "meets_endpoint_by_id": "/occasions/api/user_assembly_meets/",
"grade_converter": self.request.user.organization.infos.get('grade_converter', []) if self.request.user.organization else [],
"assemblies_endpoint": "/occasions/api/user_assemblies/",
"categories_endpoint": "/persons/api/all_categories/",
"gatherings_endpoint": "/occasions/api/organization_team_gatherings/",
Expand Down
2 changes: 2 additions & 0 deletions attendees/persons/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ class AttendingAdmin(PgHistoryPage, admin.ModelAdmin):
models.JSONField: {"widget": JSONEditorWidget},
}
search_fields = (
"id",
"attendee__first_name",
"attendee__last_name",
"attendee__first_name2",
Expand Down Expand Up @@ -325,6 +326,7 @@ class AttendingMeetAdmin(PgHistoryPage, admin.ModelAdmin):
}
list_display_links = ("attending",)
autocomplete_fields = ('attending',)
search_fields = ('infos', 'attending__attendee__infos',)
readonly_fields = ["id", "created", "modified"]
list_display = (
"id",
Expand Down
4 changes: 2 additions & 2 deletions attendees/persons/migrations/0013_folk_attendee_m2m.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class Migration(migrations.Migration):
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('is_removed', models.BooleanField(default=False)),
('display_order', models.SmallIntegerField(db_index=True, default=3000, help_text="0 will be first family")),
('display_order', models.SmallIntegerField(db_index=True, default=1, help_text="0 will be first family")),
('attendee', models.ForeignKey(on_delete=models.CASCADE, to='persons.Attendee')),
('folk', models.ForeignKey(on_delete=models.CASCADE, to='persons.Folk')),
('role', models.ForeignKey(help_text='[Title] the family role of the attendee?', on_delete=models.SET(0), related_name='role', to='persons.Relation', verbose_name='attendee is')),
Expand Down Expand Up @@ -67,7 +67,7 @@ class Migration(migrations.Migration):
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('is_removed', models.BooleanField(default=False)),
('display_order', models.SmallIntegerField(default=3000, help_text='0 will be first family')),
('display_order', models.SmallIntegerField(default=1, help_text='0 will be first family')),
('folk', models.ForeignKey(db_constraint=False, on_delete=models.deletion.DO_NOTHING, related_name='+', related_query_name='+', to='persons.folk')),
('attendee', models.ForeignKey(db_constraint=False, on_delete=models.deletion.DO_NOTHING, related_name='+', related_query_name='+', to='persons.attendee')),
('role', models.ForeignKey(db_constraint=False, help_text='[Title] the family role of the attendee?', on_delete=models.deletion.DO_NOTHING, related_name='+', related_query_name='+', to='persons.relation', verbose_name='attendee is')),
Expand Down
9 changes: 9 additions & 0 deletions attendees/persons/models/attendee.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
class Attendee(Utility, TimeStampedModel, SoftDeletableModel):
FAMILY_CATEGORY = 0
NON_FAMILY_CATEGORY = 25
PAUSED_CATEGORY = 27
SCHEDULED_CATEGORY = 1
HIDDEN_ROLE = 0
# RELATIVES_KEYWORDS = ['parent', 'mother', 'guardian', 'father', 'caregiver']
# to find attendee's parents/caregiver in cowokers view of all activities
Expand Down Expand Up @@ -212,6 +214,13 @@ def under_same_org_with(self, other_attendee_id):
).exists()
return False

def can_be_scheduled_by(self, other_attendee_id):
if str(self.id) == other_attendee_id:
return True
return Attendee.objects.filter(
pk=self.id, infos__schedulers__contains={other_attendee_id: True}
).exists()

def can_schedule_attendee(self, other_attendee_id):
if str(self.id) == other_attendee_id:
return True
Expand Down
2 changes: 1 addition & 1 deletion attendees/persons/models/attending.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def meet_names(self):
return ",".join([d.display_name for d in self.meets.all()])

@property
def attending_label(self): # parentheses needed in attendee_update_view.js for populateAttendingButtons?
def attending_label(self): # parentheses are no longer used since sorting by attendee name is needed.
return f"{self.attendee.display_label} by {self.registration}" if self.registration else self.attendee.display_label

@cached_property
Expand Down
4 changes: 2 additions & 2 deletions attendees/persons/models/folk_attendee.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class FolkAttendee(TimeStampedModel, SoftDeletableModel):
help_text="[Title] the family role of the attendee?",
)
display_order = models.SmallIntegerField(
default=3000,
default=1,
blank=False,
null=False,
db_index=True,
Expand Down Expand Up @@ -86,7 +86,7 @@ class FolkAttendeesHistory(pghistory.get_event_model(
created = model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')
modified = model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')
is_removed = models.BooleanField(default=False)
display_order = models.SmallIntegerField(default=3000, help_text='0 will be first family')
display_order = models.SmallIntegerField(default=1, help_text='0 will be first family')
folk = models.ForeignKey(db_constraint=False, on_delete=models.deletion.DO_NOTHING, related_name='+', related_query_name='+', to='persons.folk')
attendee = models.ForeignKey(db_constraint=False, on_delete=models.deletion.DO_NOTHING, related_name='+', related_query_name='+', to='persons.attendee')
role = models.ForeignKey(db_constraint=False, help_text='[Title] the family role of the attendee?', on_delete=models.deletion.DO_NOTHING, related_name='+', related_query_name='+', to='persons.relation', verbose_name='attendee is')
Expand Down
34 changes: 25 additions & 9 deletions attendees/persons/serializers/attending_minimal_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,17 @@ def create(self, validated_data):

if "registration" in validated_data:
registration_data = validated_data.pop("registration")
registration, created = Registration.objects.update_or_create(
id=None,
defaults=registration_data,
)
filters = {
'defaults': registration_data,
}
assembly = registration_data.get('assembly')
registrant = registration_data.get('registrant')

if assembly:
filters['assembly'] = assembly
if registrant:
filters['registrant'] = registrant
registration, created = Registration.objects.update_or_create(**filters)
validated_data["registration"] = registration
obj, created = Attending.objects.update_or_create(
id=None,
Expand All @@ -41,14 +48,23 @@ def create(self, validated_data):
def update(self, instance, validated_data):
"""
Update and return an existing `Attending` instance, given the validated data.
"""

if "registration" in validated_data:
registration_data = validated_data.pop("registration")
registration, created = Registration.objects.update_or_create(
id=instance.registration.id if instance.registration else None,
defaults=registration_data,
)
assembly = registration_data.get('assembly')
registrant = registration_data.get('registrant')
filters = {
'defaults': registration_data,
}
if instance.registration:
filters['id'] = instance.registration.id
if assembly:
filters['assembly'] = assembly
if registrant:
filters['registrant'] = registrant

registration, created = Registration.objects.update_or_create(**filters)
validated_data["registration"] = registration

obj, created = Attending.objects.update_or_create(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

class AttendingMeetEtcSerializer(serializers.ModelSerializer):
meet__assembly = serializers.IntegerField(read_only=True, source='assembly') # field name conversion for UI to group directly later
meet__assembly__display_order = serializers.IntegerField(read_only=True)
registrant_attendee_id = serializers.CharField(read_only=True)
attendee_id = serializers.CharField(read_only=True)
attending__registration__registrant__infos__names__original = serializers.CharField(read_only=True, source='register_name')
Expand Down
Loading

0 comments on commit ad4b7c2

Please sign in to comment.