Skip to content
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
66 changes: 3 additions & 63 deletions custom_code/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):

Expand Down Expand Up @@ -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):
Expand Down
72 changes: 34 additions & 38 deletions custom_code/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Comment on lines +940 to +942
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does deleting users work in SNEx1? I don't know if I've ever done it, so not sure what the implications would be on anything that's connected to the user. Hopefully not a cascading delete on things like observation sequences, targets, etc?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a great point, I haven't tested it with that, I'll need to go through and check this.


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.
Expand Down
3 changes: 1 addition & 2 deletions custom_code/urls.py
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -16,7 +16,6 @@
path('download-photometry/<int:targetid>/', download_photometry_view, name='download-photometry'),
path('get-target-standards/', get_target_standards_view, name='get-target-standards'),
path('tns-share-spectrum/<int:pk>/<int:datum_pk>', SNEx2SpectroscopyTNSSharePassthrough.as_view(), name='tns-share-spectrum'),
path('users/<int:pk>/update/', CustomUserUpdateView.as_view(), name='custom-user-update'),
path('target_filter/', TargetFilteringView.as_view(), name='target_filter'),

]
78 changes: 34 additions & 44 deletions custom_code/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be a dumb question, but does this run every time the user logs in, or just the first time? Doing this every time is probably overkill

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):

Expand Down
1 change: 0 additions & 1 deletion snex2/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@
path('floyds-inbox/', FloydsInboxView.as_view(), name='floyds-inbox'),
path('nonlocalizedevents/sequence/<int:id>/obs/', EventSequenceGalaxiesTripletView.as_view(), name='nonlocalizedevents-sequence-triplets'),
path('nonlocalizedevents/galaxies/<int:id>/obs/', GWFollowupGalaxyTripletView.as_view(), name='nonlocalizedevents-galaxies-triplets'),
path('snex2/accounts/approve/<int:pk>/', 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')),
Expand Down
30 changes: 0 additions & 30 deletions templates/tom_common/create_user.html

This file was deleted.

18 changes: 0 additions & 18 deletions templates/tom_registration/approve_user.html

This file was deleted.

29 changes: 0 additions & 29 deletions templates/tom_registration/partials/pending_users.html

This file was deleted.