def sync_project_memberships(request, username): """Re-sync a user's Keystone project memberships. This calls utils.auth.keystone_auth.sync_projects under the hood, which will dynamically create missing projects as well. Args: request (Request): the parent request; used for region detection. username (str): the username to sync memberships for. Return: List[keystone.Project]: a list of Keystone projects the user is a member of. """ mapper = ProjectAllocationMapper(request) try: ks_admin = admin_ks_client(request=request) ks_user = get_user(ks_admin, username) if not ks_user: logger.error(( "Could not fetch Keystone user for {}, skipping membership syncing" .format(username))) return active_projects = mapper.get_user_projects(username, alloc_status=["Active"], to_pytas_model=True) return sync_projects(ks_admin, ks_user, active_projects) except Exception as e: logger.error("Could not sync project memberships for %s: %s", username, e) return []
def add_publications(request, project_id): mapper = ProjectAllocationMapper(request) try: project = mapper.get_project(project_id) if project.source != "Chameleon" or not can_add_publications( request.user, project ): raise Http404("The requested project does not exist!") except Exception as e: logger.error(e) raise Http404("The requested project does not exist!") if request.POST: pubs_form = AddBibtexPublicationForm(request.POST) if pubs_form.is_valid(): bib_database = bibtexparser.loads(pubs_form.cleaned_data["bibtex_string"]) for entry in bib_database.entries: Publication.objects.create_from_bibtex( entry, project, request.user.username ) messages.success(request, "Publication added successfully") else: messages.error(request, "Error adding publication") pubs_form = AddBibtexPublicationForm(initial={"project_id": project.id}) return render( request, "projects/add_publications.html", { "project": project, "project_nickname": project.nickname, "is_pi": request.user.username == project.pi.username, "pubs_form": pubs_form, "form": pubs_form, }, )
def edit_type(request, project_id): form_args = {"request": request} if not request.user.is_superuser: messages.error(request, "Only the admin users can update project type.") return EditTypeForm(**form_args) form = EditTypeForm(request.POST, **form_args) if form.is_valid(request): # try to update type try: project_type_id = int(form.cleaned_data["typeId"]) ProjectAllocationMapper.update_project_type( project_id, project_type_id) form = EditTypeForm(**form_args) messages.success( request, "Update successful! Click <a href='{}'>here</a> to reload". format(request.path), ) except Exception: logger.exception("Failed to update project type") messages.error(request, "Failed to update project type") else: messages.error(request, "Failed to update project type") return form
def can_view(request, artifact): all_versions = list(artifact.versions) if artifact.sharing_key and (request.GET.get(SHARING_KEY_PARAM) == artifact.sharing_key): return all_versions if request.user.is_authenticated(): if request.user.is_staff: return all_versions if artifact.created_by == request.user: return all_versions project_shares = ShareTarget.objects.filter(artifact=artifact, project__isnull=False) # Avoid the membership lookup if there are no sharing rules in place if project_shares: mapper = ProjectAllocationMapper(request) user_projects = [ p['chargeCode'] for p in mapper.get_user_projects(request.user.username, fetch_balance=False) ] if any(p.charge_code in user_projects for p in project_shares): return all_versions # NOTE(jason): It is important that this check go last. Visibility b/c # the artifact has a DOI is the weakest form of visibility; not all # versions are necessarily visible in this case. We return a list of # the versions that are allowed. We check for stronger visibilty rules # first, as they should ensure all versions are seen. if artifact.doi: return [v for v in all_versions if v.doi] return []
def dashboard(request): context = {} # active projects... mapper = ProjectAllocationMapper(request) active_projects = mapper.get_user_projects( request.user.username, alloc_status=["Active", "Approved", "Pending"], to_pytas_model=True, ) context["active_projects"] = active_projects context["show_migration_info"] = request.session.get( "has_legacy_account", False) # open tickets... rt = rtUtil.DjangoRt() context["open_tickets"] = rt.getUserTickets(request.user.email) # ongoing outages... outages = [ o for o in Outage.objects.order_by("-end_date", "-start_date") if not o.resolved ] # silly ORM quirk context["outages"] = outages webinars = Webinar.objects.filter(end_date__gte=timezone.now()) context["webinars"] = webinars return render(request, "dashboard.html", context)
def get_all_alloc(request): """Get all allocations, grouped by project. Args: request: the request that is passed in. Raises: Exception: when loading projects fails. Returns: json: dumps all data as serialized json. """ try: keycloak_client = KeycloakClient() user_attributes = keycloak_client.get_all_users_attributes() mapper = ProjectAllocationMapper(request) resp = mapper.get_all_projects() logger.debug("Total projects: %s", len(resp)) for r in resp: pi_attributes = user_attributes.get(r["pi"]["username"], {}) if pi_attributes: institution = pi_attributes.get("affiliationInstitution", []) country = pi_attributes.get("country", []) r["pi"]["institution"] = next(iter(institution), None) r["pi"]["country"] = next(iter(country), None) except Exception as e: logger.exception("Error loading chameleon projects") messages.error(request, e) raise return json.dumps(resp)
def contact(request): resp = {} errors = {} status = "" if request.POST: mapper = ProjectAllocationMapper(request) data = json.loads(request.body) data["rt"]["owner"] = request.user.username if data["allocation"]["status"] not in ["Pending", "pending"]: errors["allocation"] = "Contacting PI when allocation is pending." if len(errors) == 0: try: mapper.contact_pi_via_rt(data) status = "success" except Exception: logger.exception("Error contacting PI.") status = "error" errors[ "message"] = "An unexpected error occurred. If this problem persists please create a help ticket." else: logger.info("Request data failed validation. %s", list(errors.values())) status = "error" else: status = "error" errors["message"] = "Only POST method allowed." resp["status"] = status resp["errors"] = errors return HttpResponse(json.dumps(resp), content_type="application/json")
def profile(request): context = {} mapper = ProjectAllocationMapper(request) context["profile"] = mapper.get_user(request.user) context["piEligibility"] = context["profile"]["piEligibility"] return render(request, "tas/profile.html", context)
def edit_nickname(request, project_id): mapper = ProjectAllocationMapper(request) project = mapper.get_project(project_id) if not project_pi_or_admin_or_superuser(request.user, project): messages.error(request, "Only the project PI can update nickname.") return EditNicknameForm() form = EditNicknameForm(request.POST) if form.is_valid(request): # try to update nickname try: nickname = form.cleaned_data["nickname"] ProjectAllocationMapper.update_project_nickname( project_id, nickname) form = EditNicknameForm() set_ks_project_nickname(project.chargeCode, nickname) messages.success( request, "Update successful! Click <a href='{}'>here</a> to reload". format(request.path), ) except: messages.error(request, "Nickname not available") else: messages.error(request, "Nickname not available") return form
def __init__(self, *args, **kwargs): if "request" in kwargs: request = kwargs["request"] del kwargs["request"] super(EditTypeForm, self).__init__(*args, **kwargs) mapper = ProjectAllocationMapper(request) self.fields["typeId"].choices = mapper.get_project_types_choices() else: logger.error("Couldn't get type list.")
def __init__(self, *args, **kwargs): if "request" in kwargs: request = kwargs["request"] del kwargs["request"] super(ProjectCreateForm, self).__init__(*args, **kwargs) mapper = ProjectAllocationMapper(request) self.fields["fieldId"].choices = mapper.get_fields_choices() else: logger.error("Couldn't get field list.")
def has_active_allocations(request): mapper = ProjectAllocationMapper(request) user_projects = mapper.get_user_projects(request.user.username, to_pytas_model=False) for project in user_projects: for allocation in project["allocations"]: if allocation["status"].lower() == "active": return True return False
def custom_login(request, current_app=None, extra_context=None): if request.GET.get(settings.FORCE_OLD_LOGIN_EXPERIENCE_PARAM) != '1': return HttpResponseRedirect(reverse('oidc_authentication_init')) login_return = login(request, current_app=None, extra_context=None) password = request.POST.get('password', False) if request.user.is_authenticated() and password: request.session['is_federated'] = False regenerate_tokens(request, password) mapper = ProjectAllocationMapper(request) mapper.lazy_add_user_to_keycloak() return login_return
def user_projects(request): context = {} username = request.user.username mapper = ProjectAllocationMapper(request) user = mapper.get_user(username) context["is_pi_eligible"] = user["piEligibility"].lower() == "eligible" context["username"] = username context["projects"] = mapper.get_user_projects(username, to_pytas_model=True) return render(request, "projects/user_projects.html", context)
def index_all(request, collection=None): user_projects = None if request.user.is_authenticated: mapper = ProjectAllocationMapper(request) user_projects = mapper.get_user_projects(request.user.username, fetch_balance=False) charge_codes = [p['chargeCode'] for p in user_projects] projects = Project.objects.filter(charge_code__in=charge_codes) f = (ArtifactFilter.MINE(request) | ArtifactFilter.PROJECT(projects) | ArtifactFilter.PUBLIC) else: f = ArtifactFilter.PUBLIC artifacts = _fetch_artifacts(f) # Pass user_projects to list to avoid a second fetch of this data return _render_list(request, artifacts, user_projects=user_projects)
def profile_edit(request): mapper = ProjectAllocationMapper(request) user_info = mapper.get_user(request.user) if request.method == "POST": request_pi_eligibility = request.POST.get("request_pi_eligibility") department_directory_link = request.POST.get("directorylink") kwargs = {"is_pi_eligible": request_pi_eligibility} form = UserProfileForm(request.POST, initial=user_info, **kwargs) if form.is_valid(): data = form.cleaned_data try: mapper.update_user_profile( request.user, data, request_pi_eligibility, department_directory_link, ) messages.success(request, "Your profile has been updated!") return HttpResponseRedirect(reverse("tas:profile")) except DuplicateUserError: messages.error(request, "A user with this email already exists") except Exception: messages.error(request, "An error occurred updating your profile") LOG.exception( ( "An unknown error occurred updating user profile for " f"{request.user.username}" ) ) else: user_info["title"] = form.data.get("title") else: kwargs = {"is_pi_eligible": False} if user_info["piEligibility"].upper() == "ELIGIBLE": kwargs = {"is_pi_eligible": True} form = UserProfileForm(initial=user_info, **kwargs) context = { "form": form, "user": user_info, "piEligibility": user_info["piEligibility"], "canRequestPI": can_request_pi(user_info.get("title", "")), } return render(request, "tas/profile_edit.html", context)
def __init__(self, request, *args, **kwargs): super(ShareArtifactForm, self).__init__(*args, **kwargs) mapper = ProjectAllocationMapper(request) user_projects = [ ( project["chargeCode"], project["nickname"] if "nickname" in project else project["chargeCode"], ) for project in mapper.get_user_projects( request.user.username, to_pytas_model=False ) ] self.fields["project"] = forms.ChoiceField( label="Belongs to project", required=False, choices=[(None, "----")] + user_projects, )
def user_projects(request, username): logger.info("User projects requested by admin: %s for user %s", request.user, username) resp = {"status": "error", "msg": "", "result": []} if username: try: mapper = ProjectAllocationMapper(request) user_projects = mapper.get_user_projects(username) resp["status"] = "success" resp["result"] = user_projects logger.info("Total chameleon projects for user %s: %s", username, len(user_projects)) except Exception as e: logger.debug("Error loading projects for user: %s with error %s", username, e) resp["msg"] = "Error loading projects for user: %s" % username return HttpResponse(json.dumps(resp), content_type="application/json")
def _render_list(request, artifacts, user_projects=None): if not user_projects: if request.user.is_authenticated: mapper = ProjectAllocationMapper(request) user_projects = mapper.get_user_projects(request.user.username, fetch_balance=False) else: user_projects = [] template = loader.get_template('sharing_portal/index.html') context = { 'hub_url': settings.ARTIFACT_SHARING_JUPYTERHUB_URL, 'artifacts': artifacts.order_by('-created_at'), 'projects': user_projects, } return HttpResponse(template.render(context, request))
def get_all_alloc(request): """Get all allocations, grouped by project. Args: request: the request that is passed in. Raises: Exception: when loading projects fails. Returns: json: dumps all data as serialized json. """ try: mapper = ProjectAllocationMapper(request) resp = mapper.get_all_projects() logger.debug("Total projects: %s", len(resp)) except Exception as e: logger.exception("Error loading chameleon projects") messages.error(request, e) raise return json.dumps(resp)
def create_supplemental_project(request, artifact): mapper = ProjectAllocationMapper(request) pi = artifact.project.pi artifact_url = request.build_absolute_uri( reverse("sharing_portal:detail", kwargs={"pk": artifact.pk})) supplemental_project = { "nickname": f"reproducing_{artifact.id}", "title": f"Reproducing '{artifact.title}'", "description": f"This project is for reproducing the artifact '{artifact.title}' {artifact_url}", "typeId": artifact.project.type.id, "fieldId": artifact.project.field.id, "piId": artifact.project.pi.id, } # Approval code is commented out during initial preview release. allocation_data = { "resourceId": 39, "requestorId": pi.id, "computeRequested": 1000, "status": "approved", #"dateReviewed": timezone.now(), #"start": timezone.now(), #"end": timezone.now() + timedelta(days=6*30), #"decisionSummary": "automatically approved for reproducibility", #"computeAllocated": 1000, #"justification": "Automatic decision", } supplemental_project["allocations"] = [allocation_data] supplemental_project["source"] = "Daypass" created_tas_project = mapper.save_project(supplemental_project, request.get_host()) # We can assume only 1 here since this project is new #allocation = Allocation.objects.get(project_id=created_tas_project["id"]) #allocation.status = "approved" #allocation.save() return Project.objects.get(id=created_tas_project["id"])
def user_publications(request): context = {} context["publications"] = [] pubs = Publication.objects.filter(added_by_username=request.user.username) for pub in pubs: project = ProjectAllocationMapper.get_publication_project(pub) if project: context["publications"].append( { "title": pub.title, "author": pub.author, "abstract": pub.abstract, "nickname": project.nickname, "chargeCode": project.charge_code, } ) return render(request, "projects/view_publications.html", context)
def accept_invite(request, invite_code): mapper = ProjectAllocationMapper(request) invitation = None try: invitation = Invitation.objects.get(email_code=invite_code) except Invitation.DoesNotExist: raise Http404("That invitation does not exist!") user = request.user # Check for existing daypass, use this invite instead daypass = get_daypass(user.id, invitation.project.id) try: if accept_invite_for_user(user, invitation, mapper): messages.success(request, "Accepted invitation") return HttpResponseRedirect( reverse("projects:view_project", args=[invitation.project.id])) else: messages.error(request, invitation.get_cant_accept_reason()) return HttpResponseRedirect(reverse("projects:user_projects")) finally: if daypass is not None: daypass.delete()
def user_publications(request): context = {} if "del_pub" in request.POST: try: del_pub_id = request.POST["pub_ref"] logger.debug("deleting publication with id {}".format(del_pub_id)) Publication.objects.get(pk=del_pub_id).delete() except Exception: logger.exception("Failed removing publication") messages.error( request, "An unexpected error occurred while attempting " "to remove this publication. Please try again", ) context["publications"] = [] pubs = Publication.objects.filter(added_by_username=request.user.username) for pub in pubs: project = ProjectAllocationMapper.get_publication_project(pub) if project: context["publications"].append( { "id": pub.id, "title": pub.title, "author": pub.author, "link": "" if not pub.link else pub.link, "forum": pub.forum, "month": "" if not pub.month else datetime.datetime.strptime(str(pub.month), "%m").strftime( "%b" ), "year": pub.year, "nickname": project.nickname, "chargeCode": project.charge_code, } ) return render(request, "projects/view_publications.html", context)
def create_allocation(request, project_id, allocation_id=-1): mapper = ProjectAllocationMapper(request) user = mapper.get_user(request.user.username) if user["piEligibility"].lower() != "eligible": messages.error( request, "Only PI Eligible users can request allocations. If you would " "like to request PI Eligibility, please " '<a href="/user/profile/edit/">submit a PI Eligibility ' "request</a>.", ) return HttpResponseRedirect(reverse("projects:user_projects")) project = mapper.get_project(project_id) allocation = None allocation_id = int(allocation_id) if allocation_id > 0: for a in project.allocations: if a.id == allocation_id: allocation = a # goofiness that we should clean up later; requires data cleansing abstract = project.description if "--- Supplemental details ---" in abstract: additional = abstract.split("\n\n--- Supplemental details ---\n\n") abstract = additional[0] additional = additional[1].split("\n\n--- Funding source(s) ---\n\n") justification = additional[0] if len(additional) > 1: funding_source = additional[1] else: funding_source = "" elif allocation: justification = allocation.justification if "--- Funding source(s) ---" in justification: parts = justification.split("\n\n--- Funding source(s) ---\n\n") justification = parts[0] funding_source = parts[1] else: funding_source = "" else: justification = "" funding_source = "" if request.POST: form = AllocationCreateForm( request.POST, initial={ "description": abstract, "supplemental_details": justification, "funding_source": funding_source, }, ) if form.is_valid(): allocation = form.cleaned_data.copy() allocation["computeRequested"] = 20000 # Also update the project project.description = allocation.pop("description", None) supplemental_details = allocation.pop("supplemental_details", None) logger.error(supplemental_details) funding_source = allocation.pop("funding_source", None) # if supplemental_details == None: # raise forms.ValidationError("Justifcation is required") # This is required if not supplemental_details: supplemental_details = "(none)" logger.error(supplemental_details) if funding_source: allocation[ "justification"] = "%s\n\n--- Funding source(s) ---\n\n%s" % ( supplemental_details, funding_source, ) else: allocation["justification"] = supplemental_details allocation["projectId"] = project_id allocation["requestorId"] = mapper.get_portal_user_id( request.user.username) allocation["resourceId"] = "39" if allocation_id > 0: allocation["id"] = allocation_id try: logger.info( "Submitting allocation request for project %s: %s" % (project.id, allocation)) updated_project = mapper.save_project(project.as_dict()) mapper.save_allocation(allocation, project.chargeCode, request.get_host()) messages.success( request, "Your allocation request has been submitted!") return HttpResponseRedirect( reverse("projects:view_project", args=[updated_project["id"]])) except: logger.exception("Error creating allocation") form.add_error( "__all__", "An unexpected error occurred. Please try again") else: form.add_error( "__all__", "There were errors processing your request. " "Please see below for details.", ) else: form = AllocationCreateForm( initial={ "description": abstract, "supplemental_details": justification, "funding_source": funding_source, }) context = { "form": form, "project": project, "alloc_id": allocation_id, "alloc": allocation, } return render(request, "projects/create_allocation.html", context)
def view_project(request, project_id): mapper = ProjectAllocationMapper(request) keycloak_client = KeycloakClient() try: project = mapper.get_project(project_id) if project.source != "Chameleon": raise Http404("The requested project does not exist!") except Exception as e: logger.error(e) raise Http404("The requested project does not exist!") form = ProjectAddUserForm() nickname_form = EditNicknameForm() type_form_args = {"request": request} type_form = EditTypeForm(**type_form_args) pubs_form = AddBibtexPublicationForm() can_manage_project_membership, can_manage_project = get_user_permissions( keycloak_client, request.user.username, project) if (request.POST and can_manage_project_membership or is_admin_or_superuser(request.user)): form = ProjectAddUserForm() if "add_user" in request.POST: form = ProjectAddUserForm(request.POST) if form.is_valid(): try: add_username = form.cleaned_data["user_ref"] user = User.objects.get(username=add_username) if mapper.add_user_to_project(project, add_username): messages.success( request, f'User "{add_username}" added to project!') form = ProjectAddUserForm() except User.DoesNotExist: # Try sending an invite email_address = form.cleaned_data["user_ref"] try: validate_email(email_address) if email_exists_on_project(project, email_address): messages.error( request, "That email is tied to a user already on the " "project!", ) else: add_project_invitation( project_id, email_address, request.user, request, None, ) messages.success(request, "Invite sent!") except ValidationError: messages.error( request, ("Unable to add user. Confirm that the username " "is correct and corresponds to a current " "Chameleon user. You can also send an invite " "to an email address if the user does not yet " "have an account."), ) except Exception: messages.error( request, "Problem sending invite, please try again.") except Exception: logger.exception("Failed adding user") messages.error(request, "Unable to add user. Please try again.") else: messages.error( request, ("There were errors processing your request. " "Please see below for details."), ) elif "del_user" in request.POST: try: del_username = request.POST["user_ref"] # Ensure that it's not possible to remove the PI if del_username in [project.pi.username, project.pi.email]: raise PermissionDenied( "Removing the PI from the project is not allowed.") if mapper.remove_user_from_project(project, del_username): messages.success( request, 'User "%s" removed from project' % del_username) user = User.objects.get(username=del_username) daypass = get_daypass(user.id, project_id) if daypass: daypass.delete() except PermissionDenied as exc: messages.error(request, exc) except Exception: logger.exception("Failed removing user") messages.error( request, "An unexpected error occurred while attempting " "to remove this user. Please try again", ) elif "change_role" in request.POST: try: role_username = request.POST["user_ref"] role_name = request.POST["user_role"].lower() keycloak_client.set_user_project_role(role_username, get_charge_code(project), role_name) except Exception: logger.exception("Failed to change user role") messages.error( request, "An unexpected error occurred while attempting " "to change role for this user. Please try again", ) elif "del_invite" in request.POST: try: invite_id = request.POST["invite_id"] remove_invitation(invite_id) messages.success(request, "Invitation removed") except Exception: logger.exception("Failed to delete invitation") messages.error( request, "An unexpected error occurred while attempting " "to remove this invitation. Please try again", ) elif "resend_invite" in request.POST: try: invite_id = request.POST["invite_id"] resend_invitation(invite_id, request.user, request) messages.success(request, "Invitation resent") except Exception: logger.exception("Failed to resend invitation") messages.error( request, "An unexpected error occurred while attempting " "to resend this invitation. Please try again") elif "nickname" in request.POST: nickname_form = edit_nickname(request, project_id) elif "typeId" in request.POST: type_form = edit_type(request, project_id) for a in project.allocations: if a.start and isinstance(a.start, str): a.start = datetime.strptime(a.start, "%Y-%m-%dT%H:%M:%SZ") if a.dateRequested: if isinstance(a.dateRequested, str): a.dateRequested = datetime.strptime(a.dateRequested, "%Y-%m-%dT%H:%M:%SZ") if a.dateReviewed: if isinstance(a.dateReviewed, str): a.dateReviewed = datetime.strptime(a.dateReviewed, "%Y-%m-%dT%H:%M:%SZ") if a.end: if isinstance(a.end, str): a.end = datetime.strptime(a.end, "%Y-%m-%dT%H:%M:%SZ") users = get_project_members(project) if not project_member_or_admin_or_superuser(request.user, project, users): raise PermissionDenied user_roles = keycloak_client.get_roles_for_all_project_members( get_charge_code(project)) users_mashup = [] for u in users: if u.username == project.pi.username: continue u_role = user_roles.get(u.username, "member") user = { "id": u.id, "username": u.username, "role": u_role.title(), } try: portal_user = User.objects.get(username=u.username) user["email"] = portal_user.email user["first_name"] = portal_user.first_name user["last_name"] = portal_user.last_name # Add if the user is on a daypass existing_daypass = get_daypass(portal_user.id, project_id) if existing_daypass: user["daypass"] = format_timedelta( existing_daypass.date_exceeds_duration() - timezone.now()) except User.DoesNotExist: logger.info("user: "******" not found") users_mashup.append(user) invitations = Invitation.objects.filter(project=project_id) invitations = [i for i in invitations if i.can_accept()] clean_invitations = [] for i in invitations: new_item = {} new_item["email_address"] = i.email_address new_item["id"] = i.id new_item["status"] = i.status.title() if i.duration: new_item["duration"] = i.duration clean_invitations.append(new_item) is_on_daypass = get_daypass(request.user.id, project_id) is not None return render( request, "projects/view_project.html", { "project": project, "project_nickname": project.nickname, "project_type": project.type, "users": users_mashup, "invitations": clean_invitations, "can_manage_project_membership": can_manage_project_membership, "can_manage_project": can_manage_project, "is_admin": request.user.is_superuser, "is_on_daypass": is_on_daypass, "form": form, "nickname_form": nickname_form, "type_form": type_form, "pubs_form": pubs_form, "roles": ROLES, "host": request.get_host(), }, )
def create_allocation(request, project_id, allocation_id=-1): mapper = ProjectAllocationMapper(request) user = mapper.get_user(request.user.username) if user["piEligibility"].lower() != "eligible": messages.error( request, "Only PI Eligible users can request allocations. If you would " "like to request PI Eligibility, please " '<a href="/user/profile/edit/">submit a PI Eligibility ' "request</a>.", ) return HttpResponseRedirect(reverse("projects:user_projects")) project = mapper.get_project(project_id) allocation = None allocation_id = int(allocation_id) if allocation_id > 0: for a in project.allocations: if a.id == allocation_id: allocation = a abstract = project.description if allocation: justification = allocation.justification else: justification = "" funding_source = [ model_to_dict(f) for f in Funding.objects.filter(project__id=project_id, is_active=True) ] # add extra form funding_source.append({}) if request.POST: form = AllocationCreateForm( request.POST, initial={ "description": abstract, "justification": justification, }, ) formset = FundingFormset( request.POST, initial=funding_source, ) consent_form = ConsentForm(request.POST) if form.is_valid() and formset.is_valid() and consent_form.is_valid(): allocation = form.cleaned_data.copy() allocation["computeRequested"] = 20000 # Also update the project and fundings project.description = allocation.pop("description", None) justification = allocation.pop("justification", None) allocation["projectId"] = project_id allocation["requestorId"] = mapper.get_portal_user_id( request.user.username) allocation["resourceId"] = "39" allocation["justification"] = justification if allocation_id > 0: allocation["id"] = allocation_id try: logger.info( "Submitting allocation request for project %s: %s" % (project.id, allocation)) with transaction.atomic(): updated_project = mapper.save_project(project.as_dict()) mapper.save_allocation(allocation, project.chargeCode, request.get_host()) new_funding_source = _save_fundings(formset, project_id) _remove_fundings(funding_source, new_funding_source) messages.success( request, "Your allocation request has been submitted!") return HttpResponseRedirect( reverse("projects:view_project", args=[updated_project["id"]])) except: logger.exception("Error creating allocation") form.add_error( "__all__", "An unexpected error occurred. Please try again") else: form.add_error( "__all__", "There were errors processing your request. " "Please see below for details.", ) else: form = AllocationCreateForm(initial={ "description": abstract, "justification": justification, }) formset = FundingFormset(initial=funding_source) consent_form = ConsentForm() context = { "form": form, "funding_formset": formset, "consent_form": consent_form, "project": project, "alloc_id": allocation_id, "alloc": allocation, } return render(request, "projects/create_allocation.html", context)
def create_project(request): mapper = ProjectAllocationMapper(request) form_args = {"request": request} user = mapper.get_user(request.user.username) if user["piEligibility"].lower() != "eligible": messages.error( request, "Only PI Eligible users can create new projects. " "If you would like to request PI Eligibility, please " '<a href="/user/profile/edit/">submit a PI Eligibility ' "request</a>.", ) return HttpResponseRedirect(reverse("projects:user_projects")) if request.POST: form = ProjectCreateForm(request.POST, **form_args) allocation_form = AllocationCreateForm( request.POST, initial={"publication_up_to_date": True}) allocation_form.fields[ "publication_up_to_date"].widget = forms.HiddenInput() funding_formset = FundingFormset(request.POST, initial=[{}]) consent_form = ConsentForm(request.POST) if (form.is_valid() and allocation_form.is_valid() and funding_formset.is_valid() and consent_form.is_valid()): # title, description, typeId, fieldId project = form.cleaned_data.copy() allocation_data = allocation_form.cleaned_data.copy() # let's check that any provided nickname is unique project["nickname"] = project["nickname"].strip() nickname_valid = (project["nickname"] and ProjectExtras.objects.filter( nickname=project["nickname"]).count() < 1 and Project.objects.filter( nickname=project["nickname"]).count() < 1) if not nickname_valid: form.add_error("__all__", "Project nickname unavailable") return render(request, "projects/create_project.html", {"form": form}) # pi pi_user_id = mapper.get_portal_user_id(request.user.username) project["piId"] = pi_user_id # allocations allocation = { "resourceId": 39, "requestorId": pi_user_id, "computeRequested": 20000, "justification": allocation_data.pop("justification", None), } project["allocations"] = [allocation] project["description"] = allocation_data.pop("description", None) # source project["source"] = "Chameleon" created_project = None try: with transaction.atomic(): created_project = mapper.save_project( project, request.get_host()) _save_fundings(funding_formset, created_project["id"]) logger.info("newly created project: " + json.dumps(created_project)) messages.success(request, "Your project has been created!") return HttpResponseRedirect( reverse("projects:view_project", args=[created_project["id"]])) except: # delete project from keycloak if created_project: keycloak_client = KeycloakClient() keycloak_client.delete_project( created_project["chargeCode"]) logger.exception("Error creating project") form.add_error( "__all__", "An unexpected error occurred. Please try again") else: form.add_error( "__all__", "There were errors processing your request. " "Please see below for details.", ) else: form = ProjectCreateForm(**form_args) allocation_form = AllocationCreateForm( initial={"publication_up_to_date": True}) allocation_form.fields[ "publication_up_to_date"].widget = forms.HiddenInput() funding_formset = FundingFormset(initial=[{}]) consent_form = ConsentForm() return render( request, "projects/create_project.html", { "form": form, "allocation_form": allocation_form, "funding_formset": funding_formset, "consent_form": consent_form, }, )
def create_project(request): mapper = ProjectAllocationMapper(request) form_args = {"request": request} user = mapper.get_user(request.user.username) if user["piEligibility"].lower() != "eligible": messages.error( request, "Only PI Eligible users can create new projects. " "If you would like to request PI Eligibility, please " '<a href="/user/profile/edit/">submit a PI Eligibility ' "request</a>.", ) return HttpResponseRedirect(reverse("projects:user_projects")) if request.POST: form = ProjectCreateForm(request.POST, **form_args) if form.is_valid(): # title, description, typeId, fieldId project = form.cleaned_data.copy() # let's check that any provided nickname is unique project["nickname"] = project["nickname"].strip() nickname_valid = (project["nickname"] and ProjectExtras.objects.filter( nickname=project["nickname"]).count() < 1 and Project.objects.filter( nickname=project["nickname"]).count() < 1) if not nickname_valid: form.add_error("__all__", "Project nickname unavailable") return render(request, "projects/create_project.html", {"form": form}) project.pop("accept_project_terms", None) # pi pi_user_id = mapper.get_portal_user_id(request.user.username) project["piId"] = pi_user_id # allocations allocation = { "resourceId": 39, "requestorId": pi_user_id, "computeRequested": 20000, } supplemental_details = project.pop("supplemental_details", None) funding_source = project.pop("funding_source", None) # if supplemental_details == None: # raise forms.ValidationError("Justifcation is required") if not supplemental_details: supplemental_details = "(none)" if funding_source: allocation[ "justification"] = "%s\n\n--- Funding source(s) ---\n\n%s" % ( supplemental_details, funding_source, ) else: allocation["justification"] = supplemental_details project["allocations"] = [allocation] # startup project["typeId"] = 2 # source project["source"] = "Chameleon" try: created_project = mapper.save_project(project, request.get_host()) logger.info("newly created project: " + json.dumps(created_project)) messages.success(request, "Your project has been created!") return HttpResponseRedirect( reverse("projects:view_project", args=[created_project["id"]])) except: logger.exception("Error creating project") form.add_error( "__all__", "An unexpected error occurred. Please try again") else: form.add_error( "__all__", "There were errors processing your request. " "Please see below for details.", ) else: form = ProjectCreateForm(**form_args) return render(request, "projects/create_project.html", {"form": form})
def view_project(request, project_id): mapper = ProjectAllocationMapper(request) try: project = mapper.get_project(project_id) if project.source != "Chameleon": raise Http404("The requested project does not exist!") except Exception as e: logger.error(e) raise Http404("The requested project does not exist!") form = ProjectAddUserForm() nickname_form = EditNicknameForm() pubs_form = AddBibtexPublicationForm() if request.POST and project_pi_or_admin_or_superuser( request.user, project): form = ProjectAddUserForm() if "add_user" in request.POST: form = ProjectAddUserForm(request.POST) if form.is_valid(): try: add_username = form.cleaned_data["username"] if mapper.add_user_to_project(project, add_username): sync_project_memberships(request, add_username) messages.success( request, f'User "{add_username}" added to project!') form = ProjectAddUserForm() except Exception as e: logger.exception("Failed adding user") messages.error( request, ("Unable to add user. Confirm that the username is " "correct and corresponds to a current Chameleon user." ), ) else: messages.error( request, ("There were errors processing your request. " "Please see below for details."), ) elif "del_user" in request.POST: try: del_username = request.POST["username"] # Ensure that it's not possible to remove the PI if del_username == project.pi.username: raise PermissionDenied( "Removing the PI from the project is not allowed.") if mapper.remove_user_from_project(project, del_username): sync_project_memberships(request, del_username) messages.success( request, 'User "%s" removed from project' % del_username) except PermissionDenied as exc: messages.error(request, exc) except: logger.exception("Failed removing user") messages.error( request, "An unexpected error occurred while attempting " "to remove this user. Please try again", ) elif "nickname" in request.POST: nickname_form = edit_nickname(request, project_id) users = mapper.get_project_members(project) if not project_member_or_admin_or_superuser(request.user, project, users): raise PermissionDenied for a in project.allocations: if a.start and isinstance(a.start, str): a.start = datetime.strptime(a.start, "%Y-%m-%dT%H:%M:%SZ") if a.dateRequested: if isinstance(a.dateRequested, str): a.dateRequested = datetime.strptime(a.dateRequested, "%Y-%m-%dT%H:%M:%SZ") if a.dateReviewed: if isinstance(a.dateReviewed, str): a.dateReviewed = datetime.strptime(a.dateReviewed, "%Y-%m-%dT%H:%M:%SZ") if a.end: if isinstance(a.end, str): a.end = datetime.strptime(a.end, "%Y-%m-%dT%H:%M:%SZ") user_mashup = [] for u in users: user = { "username": u.username, "role": u.role, } try: portal_user = User.objects.get(username=u.username) user["email"] = portal_user.email user["first_name"] = portal_user.first_name user["last_name"] = portal_user.last_name except User.DoesNotExist: logger.info("user: "******" not found") user_mashup.append(user) return render( request, "projects/view_project.html", { "project": project, "project_nickname": project.nickname, "users": user_mashup, "is_pi": request.user.username == project.pi.username, "form": form, "nickname_form": nickname_form, "pubs_form": pubs_form, }, )