diff --git a/custom_code/forms.py b/custom_code/forms.py index f5733047..8fc16ea1 100644 --- a/custom_code/forms.py +++ b/custom_code/forms.py @@ -10,16 +10,15 @@ from django.db.models.functions import Lower from django.core.exceptions import ValidationError from django.contrib.auth.models import Group -try: - from django.contrib.auth.forms import BaseUserCreationForm as UserCreationForm -except ImportError: - from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.forms import UsernameField from django.contrib.auth.models import User, Group from django.db import transaction from crispy_forms.helper import FormHelper import re +import logging + +logger = logging.getLogger(__name__) class CustomTargetCreateForm(SiderealTargetCreateForm): @@ -72,65 +71,6 @@ def save(self, commit=True): ) return instance - -class SNEx2UserCreationForm(UserCreationForm): - """ - Form used for creation of new users and update of existing users. - """ - email = forms.EmailField(required=True) - groups = forms.ModelMultipleChoiceField(Group.objects.all(), - required=False, widget=forms.CheckboxSelectMultiple) - - class Meta: - model = User - fields = ('username', 'first_name', 'last_name', 'email', 'groups') - field_classes = {'username': UsernameField} - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - self.helper = FormHelper() - self.form_tag = False - - def save(self, commit=True): - # If any operations fail, we roll back - with transaction.atomic(): - # Saving the MaterialRequisition first - user = super(forms.ModelForm, self).save(commit=False) - - # Because this form is used for both create and update user, and the user can be updated without modifying - # the password, we check if the password field has been populated in order to set a new one. - if self.cleaned_data['password1']: - user.set_password(self.cleaned_data["password1"]) - if commit: - # Saving the inline formsets - user.save() - self.save_m2m() - - return user - - # Also needs to be overridden in case any clean method are implemented - def clean(self): - super().clean() - - return self.cleaned_data - - # is_valid sets the cleaned_data attribute so we need to override that too - def is_valid(self): - is_valid = True - is_valid &= super().is_valid() - - return is_valid - - # In case you're using the form for updating, you need to do this too - # because nothing will be saved if you only update field in the inner formset - def has_changed(self): - has_changed = False - - has_changed |= super().has_changed() - - return has_changed - class ReducerGroupWidget(forms.widgets.MultiWidget): def __init__(self, attrs=None): diff --git a/custom_code/hooks.py b/custom_code/hooks.py index 7e062da6..07edec49 100644 --- a/custom_code/hooks.py +++ b/custom_code/hooks.py @@ -918,58 +918,54 @@ def get_standards_from_snex1(target_id): return [dict(r._mapping) for r in standard_info] - -def sync_users_with_snex1(user, created=False, old_username=''): +def sync_users_with_snex1(user, delete=False, snex1_pw=''): """ Sync a new or updated user with SNEx1 Parameters ---------- user: User database object - created: boolean, True if this user was just created - old_username: str, to track the old username of the user prior to update + delete: boolean, True if the user was just deleted + snex1_pw: str, to pass the snex1 password to supernova db """ - with _get_session(db_address=settings.SNEX1_DB_URL) as db_session: - Groups = _load_table('groups', db_address=settings.SNEX1_DB_URL) Users = _load_table('users', db_address=settings.SNEX1_DB_URL) - # Get all groups this user belongs to - groups = user.groups.all() - - # Get the idcodes from the groups in the group_list - groupidcode = 0 - for group in groups: - groupidcode += int(db_session.query(Groups).filter(Groups.name==group.name).first().idcode) - - if created: - newuser = Users( - name=user.username, - pw=user.password, ### Hash? - groupidcode=groupidcode, - firstname=user.first_name, - lastname=user.last_name, - email=user.email, - datecreated=user.date_joined - ) - db_session.add(newuser) - db_session.commit() + group_names = [g.name for g in user.groups.all()] + snex1_groups = db_session.query(Groups).filter(Groups.name.in_(group_names)).all() + + groupidcode = sum(int(g.idcode) for g in snex1_groups) + if delete: + db_session.query(Users).filter(Users.name == user.username).delete() + logger.info(f'User {user.username} deleted from SNEx1') + else: - db_session.query(Users).filter( - Users.name==old_username - ).update( - {'name': user.username, - # 'pw': 'crypt$$'+user.password, - 'firstname': user.first_name, - 'lastname': user.last_name, - 'email': user.email} - ) - db_session.commit() - - logger.info('Synced user {} with SNEx1'.format(user.username)) + existing_user = db_session.query(Users).filter(Users.name == user.username).first() + + if existing_user: + existing_user.pw = snex1_pw + existing_user.firstname = user.first_name + existing_user.lastname = user.last_name + existing_user.email = user.email + existing_user.groupidcode = groupidcode + logger.info(f'User {user.username} updated in SNEx1') + else: + new_user = Users( + name=user.username, + pw=snex1_pw, + groupidcode=groupidcode, + firstname=user.first_name, + lastname=user.last_name, + email=user.email, + datecreated=user.date_joined + ) + db_session.add(new_user) + logger.info(f'User {user.username} created in SNEx1') + db_session.commit() + def download_test_image_from_archive(): """ Download a test image from the LCO archive to test image thumbnails. diff --git a/custom_code/urls.py b/custom_code/urls.py index c304d8ad..3a1c3cdc 100644 --- a/custom_code/urls.py +++ b/custom_code/urls.py @@ -1,6 +1,6 @@ from django.urls import path -from custom_code.views import TNSTargets, PaperCreateView, scheduling_view, ReferenceStatusUpdateView, ObservationGroupDetailView, observation_sequence_cancel_view, approve_or_reject_observation_view, AuthorshipInformation, download_photometry_view, get_target_standards_view, SNEx2SpectroscopyTNSSharePassthrough, CustomUserUpdateView, TargetFilteringView +from custom_code.views import TNSTargets, PaperCreateView, scheduling_view, ReferenceStatusUpdateView, ObservationGroupDetailView, observation_sequence_cancel_view, approve_or_reject_observation_view, AuthorshipInformation, download_photometry_view, get_target_standards_view, SNEx2SpectroscopyTNSSharePassthrough, TargetFilteringView app_name = 'custom_code' @@ -16,7 +16,6 @@ path('download-photometry//', download_photometry_view, name='download-photometry'), path('get-target-standards/', get_target_standards_view, name='get-target-standards'), path('tns-share-spectrum//', SNEx2SpectroscopyTNSSharePassthrough.as_view(), name='tns-share-spectrum'), - path('users//update/', CustomUserUpdateView.as_view(), name='custom-user-update'), path('target_filter/', TargetFilteringView.as_view(), name='target_filter'), ] diff --git a/custom_code/views.py b/custom_code/views.py index e75a38a5..0024104c 100644 --- a/custom_code/views.py +++ b/custom_code/views.py @@ -20,7 +20,6 @@ from tom_targets.models import TargetList, Target, TargetName from custom_code.models import TNSTarget, ScienceTags, TargetTags, ReducedDatumExtra, Papers, InterestedPersons, BrokerTarget from custom_code.filters import TNSTargetFilter, CustomTargetFilter, BrokerTargetFilter, BrokerTargetForm -from custom_code.forms import SNEx2UserCreationForm from guardian.mixins import PermissionListMixin from guardian.models import GroupObjectPermission from guardian.shortcuts import get_objects_for_user, assign_perm, remove_perm, get_users_with_perms @@ -29,6 +28,8 @@ from django.contrib import messages from django.conf import settings from django.db.models.fields.json import KeyTextTransform +from django.contrib.auth.signals import user_logged_in +import crypt from astropy.coordinates import SkyCoord from astropy import units as u @@ -346,50 +347,39 @@ def get_initial(self): 'groups': Group.objects.filter(name__in=settings.DEFAULT_GROUPS), **dict(self.request.GET.items()) } - - -class CustomUserUpdateView(UserUpdateView): - - form_class = SNEx2UserCreationForm - - def get_success_url(self): - """ - Returns the redirect URL for a successful update. If the current user is a superuser, returns the URL for the - user list. Otherwise, returns the URL for updating the current user. - - :returns: URL for user list or update user - :rtype: str - """ - if self.request.user.is_superuser: - return reverse_lazy('user-list') - else: - return reverse_lazy('custom_code:custom-user-update', kwargs={'pk': self.request.user.id}) - - def dispatch(self, *args, **kwargs): - """ - Directs the class-based view to the correct method for the HTTP request method. Ensures that non-superusers - are not incorrectly updating the profiles of other users. - """ - if not self.request.user.is_superuser and self.request.user.id != self.kwargs['pk']: - return redirect('custom_code:custom-user-update', self.request.user.id) - else: - return super().dispatch(*args, **kwargs) - - def form_valid(self, form): - old_username = self.get_object().username - super().form_valid(form) - run_hook('sync_users_with_snex1', self.get_object(), False, old_username) - return redirect(self.get_success_url()) - - -class SNEx2UserApprovalView(UserApprovalView): - - def form_valid(self, form): - response = super().form_valid(form) - run_hook("sync_users_with_snex1", self.get_object(), True) - - return response +def encrypt_pw(pw=None): + if not pw: + return None + + chars = 'abcdefghijklmnopqrstuvwxyz' \ + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' \ + '0123456789' + + salt = '' + for i in range(2): + salt += random.choice(chars) + + encpw = crypt.crypt(pw, salt) + + return encpw + +@receiver(user_logged_in) +def snex1_pw_sync(sender, request, user, **kwargs): + logger.info(f"Receiver called, User {user.username} has logged in. request: {request}") + password = request.POST.get("password") + logger.info(f"password: {password}") + snex1_pw = encrypt_pw(password) + logger.info(f'snex1pw: {snex1_pw}') + run_hook('sync_users_with_snex1', user, snex1_pw = snex1_pw) + +@receiver(post_delete, sender=User) +def user_account_remove(sender, instance, **kwargs): + logger.info(f'user account deleted {instance.username}') + logger.info(f'what is instance? {instance}') + logger.info(f'type instance? {type(instance)}') + logger.info(f'keys for instance? {dir(instance)}') + run_hook('sync_users_with_snex1', instance, delete = True) class CustomDataProductUploadView(DataProductUploadView): diff --git a/snex2/urls.py b/snex2/urls.py index c9f830bd..896b11c2 100644 --- a/snex2/urls.py +++ b/snex2/urls.py @@ -82,7 +82,6 @@ path('floyds-inbox/', FloydsInboxView.as_view(), name='floyds-inbox'), path('nonlocalizedevents/sequence//obs/', EventSequenceGalaxiesTripletView.as_view(), name='nonlocalizedevents-sequence-triplets'), path('nonlocalizedevents/galaxies//obs/', GWFollowupGalaxyTripletView.as_view(), name='nonlocalizedevents-galaxies-triplets'), - path('snex2/accounts/approve//', SNEx2UserApprovalView.as_view(), name="snex2-approve-user"), path('snex2/', include('custom_code.urls')), path('nonlocalizedevents/', include('tom_nonlocalizedevents.urls', namespace='nonlocalizedevents')), path('django_plotly_dash/', include('django_plotly_dash.urls')), diff --git a/templates/tom_common/create_user.html b/templates/tom_common/create_user.html deleted file mode 100644 index 8e1694dc..00000000 --- a/templates/tom_common/create_user.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends 'tom_common/base.html' %} -{% load bootstrap4 static %} -{% block title %}Create user{% endblock %} -{% block additional_css %} - -{% endblock %} -{% block content %} - -{% if object %} -
-{% else %} - -{% endif %} -{% csrf_token %} -{% bootstrap_form form %} -{% buttons %} - -{% endbuttons %} -
-{% endblock %} diff --git a/templates/tom_registration/approve_user.html b/templates/tom_registration/approve_user.html deleted file mode 100644 index 7526877c..00000000 --- a/templates/tom_registration/approve_user.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends 'tom_common/base.html' %} -{% load bootstrap4 static %} -{% block title %}Approve user{% endblock %} -{% block additional_css %} - -{% endblock %} -{% block content %} -
-{% csrf_token %} -{% bootstrap_form form %} -{% bootstrap_formset form.user_profile_formset %} -{% buttons %} - -{% endbuttons %} -
-{% endblock %} \ No newline at end of file diff --git a/templates/tom_registration/partials/pending_users.html b/templates/tom_registration/partials/pending_users.html deleted file mode 100644 index e1e001a5..00000000 --- a/templates/tom_registration/partials/pending_users.html +++ /dev/null @@ -1,29 +0,0 @@ -{% if request.user.is_superuser %} -

Pending Users

-
-
- - - - - - - - - - - - {% for user in users %} - - - - - - - - {% endfor %} - -
UsernameNameEmailApproveDelete
{{ user.username }}{{ user.first_name }} {{ user.last_name }}{{ user.email }}ApproveDelete
-
-
-{% endif %} \ No newline at end of file