sighn_up_in_out
This commit is contained in:
parent
810391d6fc
commit
a26ef551ee
Binary file not shown.
Binary file not shown.
@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/5.1/ref/settings/
|
||||
|
||||
from pathlib import Path
|
||||
import os
|
||||
from django.urls import reverse_lazy
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
@ -141,3 +142,20 @@ MEDIA_ROOT = BASE_DIR / 'media'
|
||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
|
||||
AUTH_URLS = {
|
||||
'login': reverse_lazy('login'),
|
||||
'logout': reverse_lazy('logout'),
|
||||
'password_reset': reverse_lazy('password_reset'),
|
||||
# ... other auth URLs
|
||||
}
|
||||
|
||||
#email Setup
|
||||
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
EMAIL_HOST = 'smtp.gmail.com' # Or your SMTP host
|
||||
EMAIL_PORT = 587
|
||||
EMAIL_USE_TLS = True
|
||||
EMAIL_HOST_USER = 'dpmehealth@gmail.com'
|
||||
EMAIL_HOST_PASSWORD = 'qxyt faeu uioy jbjs'
|
@ -17,7 +17,9 @@ Including another URLconf
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
import resulation
|
||||
from django.contrib.auth.views import LoginView, LogoutView
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('rcm/', include('resulation.urls')),
|
||||
path('rcm/', include('django.contrib.auth.urls')),
|
||||
]
|
||||
|
BIN
resulation/__pycache__/forms.cpython-311.pyc
Normal file
BIN
resulation/__pycache__/forms.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
8
resulation/forms.py
Normal file
8
resulation/forms.py
Normal file
@ -0,0 +1,8 @@
|
||||
from django import forms
|
||||
from django.contrib.auth.forms import UserCreationForm
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
class SignupForm(UserCreationForm):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['username', 'email']
|
39
resulation/migrations/0010_verification.py
Normal file
39
resulation/migrations/0010_verification.py
Normal file
@ -0,0 +1,39 @@
|
||||
# Generated by Django 5.1.5 on 2025-02-20 03:07
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("resulation", "0009_alter_resulation_attendance_file_and_more"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Verification",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("key", models.CharField(max_length=50)),
|
||||
("verified", models.BooleanField(default=False)),
|
||||
(
|
||||
"user",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
Binary file not shown.
@ -27,4 +27,13 @@ class resulation(models.Model):
|
||||
verbose_name_plural = "PDF Documents"
|
||||
|
||||
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
class Verification(models.Model):
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||
key = models.CharField(max_length=50)
|
||||
verified = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user.username}'s verification"
|
@ -6,6 +6,9 @@ from django.conf.urls.static import static
|
||||
from django.urls import path, re_path
|
||||
from . import views
|
||||
from django.views.static import serve
|
||||
from .views import SignupView
|
||||
from django.contrib.auth.views import LoginView, LogoutView
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('', resulation.views.Home, name='home'),
|
||||
@ -17,6 +20,10 @@ urlpatterns = [
|
||||
path('viewmou/', resulation.views.Viewmou, name='viewmou'),
|
||||
path('<str:type>/<str:filename>', resulation.views.file_download, name='file-download'),
|
||||
path('rcm/', resulation.views.file_download, name='download_file'),
|
||||
path('signup/', SignupView.as_view(), name='signup'),
|
||||
path('verify/<str:key>/', resulation.views.Verify, name='verify'),
|
||||
path('login/', resulation.views.signin, name='login'),
|
||||
path('logout/', resulation.views.signout, name='logout'),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
|
@ -7,11 +7,151 @@ from django.http import FileResponse, StreamingHttpResponse
|
||||
from urllib.parse import unquote
|
||||
from misdghs import settings
|
||||
from django.core.files.storage import default_storage
|
||||
from django.shortcuts import render, redirect
|
||||
from django.urls import reverse_lazy, reverse
|
||||
from django.contrib.auth.views import LoginView
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.views.generic.edit import CreateView
|
||||
from .models import Verification
|
||||
from .forms import SignupForm
|
||||
from django.contrib.auth import authenticate, login, logout
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib import messages
|
||||
|
||||
|
||||
class SignupView(SuccessMessageMixin, CreateView):
|
||||
form_class = SignupForm
|
||||
template_name = 'registration/signup.html'
|
||||
success_url = reverse_lazy('login')
|
||||
success_message = "Registration successful! Please verify your email."
|
||||
|
||||
def form_valid(self, form):
|
||||
print("=== Checking form validity in SignupView ===")
|
||||
|
||||
# Check if the username already exists
|
||||
existing_username = User.objects.filter(username=form.cleaned_data['username'])
|
||||
if existing_username.exists():
|
||||
print(f"Username '{form.cleaned_data['username']}' already exists.")
|
||||
form.add_error('username', 'Username already taken.')
|
||||
return super().form_invalid(form)
|
||||
|
||||
# Check if the email already exists
|
||||
existing_email = User.objects.filter(email=form.cleaned_data['email'])
|
||||
if existing_email.exists():
|
||||
print(f"Email '{form.cleaned_data['email']}' already registered.")
|
||||
form.add_error('email', 'Email already registered.')
|
||||
return super().form_invalid(form)
|
||||
|
||||
# Form is valid, proceed to create user
|
||||
print("Form validation passed. Proceeding to create user...")
|
||||
response = super().form_valid(form)
|
||||
print(f"User created successfully. User object: {self.object}")
|
||||
|
||||
# Verify that self.object exists and has the correct fields
|
||||
if self.object:
|
||||
print(f"New user details - Username: {self.object.username}, Email: {self.object.email}")
|
||||
print("Proceeding to send verification email.")
|
||||
self.send_verification_email()
|
||||
else:
|
||||
print("Error: User object is not created after form submission.")
|
||||
|
||||
return response
|
||||
|
||||
def send_verification_email(self):
|
||||
print("\n=== Entering send_verification_email method ===")
|
||||
try:
|
||||
# Create a new Verification instance
|
||||
verification = Verification.objects.create(user=self.object)
|
||||
print(f"Verification object created with key: {verification.key}")
|
||||
|
||||
# Generate the verification key (you can improve this by using a more secure method)
|
||||
import uuid
|
||||
key = str(uuid.uuid4())
|
||||
print(f"Generated verification key: {key}")
|
||||
|
||||
# Prepare email content
|
||||
subject = 'Verify your email'
|
||||
body = (
|
||||
f'Please verify your account by clicking this link: '
|
||||
f'{self.request.build_absolute_uri(reverse("verify", kwargs={"key": key}))}'
|
||||
)
|
||||
html_body = (
|
||||
f'<a href="{self.request.build_absolute_uri(reverse("verify", kwargs={"key": key}))}">'
|
||||
'Verify Email</a>'
|
||||
)
|
||||
|
||||
# Send email using Django's send_mail function
|
||||
print(f"Attempting to send verification email to: {self.object.email}")
|
||||
from django.core.mail import send_mail
|
||||
send_mail(
|
||||
subject,
|
||||
body,
|
||||
'dpmehealth@gmail.com',
|
||||
[self.object.email],
|
||||
fail_silently=False
|
||||
)
|
||||
print("Verification email sent successfully.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error sending verification email: {str(e)}")
|
||||
raise # Re-raise the exception to see it in logs or handle appropriately
|
||||
|
||||
def generate_key(self):
|
||||
import uuid
|
||||
return str(uuid.uuid4())
|
||||
|
||||
def Verify(request, key):
|
||||
try:
|
||||
print(f"\n=== Verification view called with key: {key} ===")
|
||||
verification = Verification.objects.get(key=key)
|
||||
if not verification.verified:
|
||||
print(f"Verification found and unverified. Proceeding to verify.")
|
||||
verification.verified = True
|
||||
verification.save()
|
||||
user = verification.user
|
||||
print(f"User associated with this verification: {user.username}")
|
||||
user.is_active = True
|
||||
user.save()
|
||||
messages.success(request, 'Email verified successfully!')
|
||||
return redirect('login')
|
||||
else:
|
||||
print("Verification link already used.")
|
||||
messages.error(request, 'Already verified.')
|
||||
except Verification.DoesNotExist:
|
||||
print(f"Invalid verification key: {key}")
|
||||
messages.error(request, 'Invalid verification link.')
|
||||
return redirect('home')
|
||||
|
||||
|
||||
def signin(request):
|
||||
if request.method == "POST":
|
||||
username = request.POST.get("username")
|
||||
password = request.POST.get("password")
|
||||
user = authenticate(request, username=username, password=password)
|
||||
if user is not None:
|
||||
login(request, user)
|
||||
messages.success(request, "You have successfully logged in!")
|
||||
return redirect("home")
|
||||
messages.error(request, "Invalid username or password.")
|
||||
else:
|
||||
return render(request, "registration/signin.html")
|
||||
|
||||
@login_required
|
||||
def signout(request):
|
||||
logout(request)
|
||||
messages.info(request, "You have successfully logged out.")
|
||||
return redirect("home")
|
||||
|
||||
|
||||
|
||||
|
||||
# Create your views here.
|
||||
def Home(request):
|
||||
return render(request,'resulation/home.html')
|
||||
|
||||
|
||||
@login_required
|
||||
def Savepdf(request):
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
@ -71,12 +211,13 @@ def Savepdf(request):
|
||||
# Render the form template
|
||||
return render(request, 'resulation/resulation.html')
|
||||
|
||||
@login_required
|
||||
def Viewresulation(request):
|
||||
# Get all records from database
|
||||
pdf_records = resulation.objects.filter(pdftype='Meeting Minutes').order_by('-m_date') # Latest first
|
||||
return render(request, 'resulation/viewresulation.html', {'pdf_records': pdf_records})
|
||||
|
||||
|
||||
@login_required
|
||||
def file_download(request, type, filename):
|
||||
# Construct the full path to the file
|
||||
if type== 'attendance':
|
||||
@ -89,8 +230,7 @@ def file_download(request, type, filename):
|
||||
return FileResponse(open(file_path, 'rb'), content_type='application/pdf')
|
||||
|
||||
|
||||
|
||||
|
||||
@login_required
|
||||
def Mou(request):
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
@ -131,6 +271,8 @@ def Mou(request):
|
||||
# Render the form template
|
||||
return render(request, 'resulation/mou.html')
|
||||
|
||||
|
||||
@login_required
|
||||
def Viewmou(request):
|
||||
# Get all records from database
|
||||
pdf_records = resulation.objects.filter(pdftype='MoU').order_by('-m_date') # Latest first
|
||||
@ -138,7 +280,7 @@ def Viewmou(request):
|
||||
return render(request, 'resulation/viewmou.html', {'pdf_records': pdf_records})
|
||||
|
||||
|
||||
|
||||
@login_required
|
||||
def Contract(request):
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
@ -185,6 +327,8 @@ def Contract(request):
|
||||
# Render the form template
|
||||
return render(request, 'resulation/contract.html')
|
||||
|
||||
|
||||
@login_required
|
||||
def Viewcontract(request):
|
||||
# Get all records from database
|
||||
pdf_records = resulation.objects.filter(pdftype='Contract').order_by('-m_date') # Latest first
|
||||
|
@ -19,9 +19,13 @@
|
||||
|
||||
<div class="menu-container">
|
||||
<a href="http://localhost:8000/rcm">Home</a>
|
||||
|
||||
<a href="http://localhost:8000/rcm/login">Sign in</a>
|
||||
<a href="http://localhost:8000/rcm/signup">Sign up</a>
|
||||
{% if user.is_authenticated %}
|
||||
<a href="http://localhost:8000/rcm">Home</a>
|
||||
<div class="dropdown">
|
||||
<a href="#" class="dropdown-btn">Upload</a>
|
||||
|
||||
<div class="dropdown-content">
|
||||
<a href="http://localhost:8000/rcm/resulation">Resulation</a>
|
||||
<a href="http://localhost:8000/rcm/deed">Contract</a>
|
||||
@ -34,7 +38,7 @@
|
||||
<a href="http://localhost:8000/rcm/viewresulation">Resulations</a>
|
||||
<a href="http://localhost:8000/rcm/viewdeed">Contracts</a>
|
||||
<a href="http://localhost:8000/rcm/viewmou">MoU</a>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
29
templates/registration/signin.html
Normal file
29
templates/registration/signin.html
Normal file
@ -0,0 +1,29 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="form-container">
|
||||
<h2>Login</h2>
|
||||
{% if messages %}
|
||||
<ul class="messages">
|
||||
{% for message in messages %}
|
||||
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<form method="post">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" name="username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" name="password" required>
|
||||
</div>
|
||||
{% csrf_token %}
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
<p>Don't have an account? <a href="{% url 'signup' %}">Register here</a></p>
|
||||
</div>
|
||||
{% endblock %}
|
220
templates/registration/signup.html
Normal file
220
templates/registration/signup.html
Normal file
@ -0,0 +1,220 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #f4f6f9;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
padding: 20px;
|
||||
background: #ffffff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #2c3e50;
|
||||
text-align: center;
|
||||
border-bottom: 3px solid #3498db;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #2980b9;
|
||||
margin-top: 25px;
|
||||
border-left: 4px solid #3498db;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: justify;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
list {
|
||||
display: block;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
list li {
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
list li::before {
|
||||
content: '•';
|
||||
color: #3498db;
|
||||
font-weight: bold;
|
||||
position: absolute;
|
||||
left: -20px;
|
||||
}
|
||||
|
||||
.form-title {
|
||||
color: #2c3e50;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-table {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0 15px;
|
||||
}
|
||||
|
||||
.form-table th {
|
||||
text-align: left;
|
||||
padding: 8px;
|
||||
color: #34495e;
|
||||
font-weight: 600;
|
||||
width: 30%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.form-table input[type="text"],
|
||||
.form-table input[type="date"] {
|
||||
width: 90%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.form-table input[type="text"]:focus,
|
||||
.form-table input[type="date"]:focus {
|
||||
border-color: #3498db;
|
||||
outline: none;
|
||||
box-shadow: 0 0 5px rgba(52,152,219,0.3);
|
||||
}
|
||||
|
||||
.form-table input[type="file"] {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.submit-btn:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
.file-input-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.file-input-label {
|
||||
display: inline-block;
|
||||
background-color: #f8f9fa;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.alert {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
padding: 15px;
|
||||
margin: 10px 0;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #c3e6cb;
|
||||
animation: fadeOut 5s forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
0% { opacity: 1; }
|
||||
90% { opacity: 1; }
|
||||
100% { opacity: 0; }
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.form-container {
|
||||
margin: 10px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.form-table th {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.form-table td {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% if messages %}
|
||||
<div class="messages">
|
||||
{% for message in messages %}
|
||||
<div{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-container">
|
||||
<h3 class="form-title">Sign Up</h3>
|
||||
<form action="{% url "signup" %}" method="POST" enctype="multipart/form-data">
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th align="left">Userneme:</th>
|
||||
<td><input type="text" id="username" name="username" required></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th align="left">Email:</th>
|
||||
<td><input type="email" id="email" name="email" required></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th align="left">Password:</th>
|
||||
<td><input type="password" id="password1" name="password1" required></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th align="left">Confirm Password:</th>
|
||||
<td><input type="password" id="password2" name="password2" required></td>
|
||||
</tr>
|
||||
{% csrf_token %}
|
||||
<tr>
|
||||
<th></th>
|
||||
<td strong center><input Type="Submit"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> Already have an account? <a href="{% url 'login' %}">Login here</a></th>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock content %}
|
14
templates/registration/verification_email.html
Normal file
14
templates/registration/verification_email.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!-- registration/verification_email.html -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Email Verification</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Please verify your email</h1>
|
||||
<p>Click this link to verify your account:</p>
|
||||
<a href="{{ verification_link }}">Verify Now</a>
|
||||
<br>
|
||||
<p>If you didn't request this, you can safely ignore this email.</p>
|
||||
</body>
|
||||
</html>
|
@ -1,7 +1,8 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
{% if message %}
|
||||
<div class="alert alert-success" style="background-color: #d4edda; color: #155724; padding: 15px; margin: 10px 0; border-radius: 4px; border: 1px solid #c3e6cb;">
|
||||
<div class="alert alert-success"
|
||||
style="background-color: #d4edda; color: #155724; padding: 15px; margin: 10px 0; border-radius: 4px; border: 1px solid #c3e6cb;">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -86,6 +87,7 @@ list li::before {
|
||||
}
|
||||
</style>
|
||||
|
||||
</div>
|
||||
<h1 strong align='center'>Archiving and Timely Upload of Meeting Minutes, Deeds, and MoUs</h1>
|
||||
<br><p>To ensure seamless access and efficient management of critical documents within the Directorate General of Health Services (DGHS), it is essential to archive and upload Meeting Minutes, Deeds, and Memorandums of Understanding (MOUs) in a timely manner.
|
||||
</p>
|
||||
|
Loading…
Reference in New Issue
Block a user