mirror of
https://github.com/kristoferssolo/FOSSDB.git
synced 2025-10-21 17:50:35 +00:00
commit
27721e277d
@ -1,3 +0,0 @@
|
||||
# from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@ -1,3 +0,0 @@
|
||||
# from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
@ -1,3 +0,0 @@
|
||||
# from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@ -1,3 +0,0 @@
|
||||
# from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
@ -2,5 +2,5 @@ Django==4.2.2
|
||||
Pillow==9.5.0
|
||||
fontawesomefree==6.4.0
|
||||
mysqlclient==2.1.1
|
||||
django-filter==23.2
|
||||
django-tailwind==3.6.0
|
||||
django-search-views==0.3.7
|
||||
|
||||
34
setup.cfg
34
setup.cfg
@ -1,34 +0,0 @@
|
||||
[metadata]
|
||||
name = FOSSDB-Web
|
||||
description = Free and Open-Source Software Database Website
|
||||
author = Kristofers Solo
|
||||
license = GPL3
|
||||
license_files = LICENSE
|
||||
platforms = unix, linux, osx, cygwin, win32
|
||||
classifiers =
|
||||
Programming Language :: Python :: 3.10
|
||||
|
||||
[options]
|
||||
packages = FOSSDB_web
|
||||
install_requires =
|
||||
Django>=4.1
|
||||
fontawesomefree>=6.2.1
|
||||
mysqlclient>=2.1.1
|
||||
|
||||
python_requires = >=3.10
|
||||
package_dir = =.
|
||||
zip_safe = no
|
||||
|
||||
[options.extras_require]
|
||||
testing =
|
||||
flake8>=6.0.0
|
||||
mypy>=0.991
|
||||
pytest-cov>=4.0.0
|
||||
pytest>=7.2.0
|
||||
tox>=3.27.1
|
||||
|
||||
[options.package_data]
|
||||
detector = py.typed
|
||||
|
||||
[flake8]
|
||||
max-line-length = 160
|
||||
@ -38,10 +38,9 @@ INSTALLED_APPS = [
|
||||
"main",
|
||||
"account",
|
||||
"fossdb",
|
||||
"django_filters",
|
||||
"search_views",
|
||||
"tailwind",
|
||||
"tokyonight_night",
|
||||
"crispy_forms",
|
||||
"fontawesomefree",
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
@ -51,6 +50,13 @@ INSTALLED_APPS = [
|
||||
"django.contrib.staticfiles",
|
||||
]
|
||||
|
||||
TAILWIND_APP_NAME = "tokyonight_night"
|
||||
|
||||
INTERNAL_IPS = [
|
||||
"127.0.0.1",
|
||||
]
|
||||
|
||||
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
@ -157,12 +163,6 @@ LOGOUT_REDIRECT_URL = "/"
|
||||
LOGIN_URL = "/login/"
|
||||
|
||||
|
||||
TAILWIND_APP_NAME = "tokyonight_night"
|
||||
|
||||
INTERNAL_IPS = [
|
||||
"127.0.0.1",
|
||||
]
|
||||
|
||||
# HTTPS settings
|
||||
# SESSION_COOKIE_SECURE = True
|
||||
# CSRF_COOKIE_SECURE = True
|
||||
|
||||
@ -19,10 +19,9 @@ from django.urls import include, path
|
||||
|
||||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
path("", include("fossdb.urls")),
|
||||
path("", include("main.urls")),
|
||||
path("", include("account.urls")),
|
||||
path("", include("django.contrib.auth.urls")),
|
||||
path("auth/", include("account.urls")),
|
||||
path("<str:owner>/<str:project_name>/", include("fossdb.urls")),
|
||||
]
|
||||
if settings.DEBUG:
|
||||
from django.conf.urls.static import static
|
||||
|
||||
@ -1,12 +1,31 @@
|
||||
from django import forms
|
||||
|
||||
from django.contrib.auth.forms import UserCreationForm
|
||||
from django.contrib.auth.forms import AuthenticationForm, UserChangeForm as BaseUserChangeForm, UserCreationForm
|
||||
|
||||
from .models import User
|
||||
from .models import Profile, User
|
||||
|
||||
|
||||
class LoginForm(AuthenticationForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(LoginForm, self).__init__(*args, **kwargs)
|
||||
self.fields["username"].widget = forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": "Username",
|
||||
"class": "verify form-field submit-form",
|
||||
}
|
||||
)
|
||||
self.fields["username"].label = ""
|
||||
self.fields["password"].widget = forms.PasswordInput(
|
||||
attrs={
|
||||
"placeholder": "Password",
|
||||
"class": "verify form-field submit-form",
|
||||
}
|
||||
)
|
||||
self.fields["password"].label = ""
|
||||
|
||||
|
||||
class SignUpForm(UserCreationForm):
|
||||
email = forms.EmailField(required=False, help_text="Optional.")
|
||||
email = forms.EmailField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
@ -16,3 +35,99 @@ class SignUpForm(UserCreationForm):
|
||||
"password1",
|
||||
"password2",
|
||||
)
|
||||
|
||||
widgets = {
|
||||
"username": forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": "Username",
|
||||
"class": "verify form-field submit-form",
|
||||
}
|
||||
),
|
||||
"email": forms.EmailInput(
|
||||
attrs={
|
||||
"placeholder": "Email (optional)",
|
||||
"class": "verify form-field submit-form",
|
||||
}
|
||||
),
|
||||
"password1": forms.PasswordInput(
|
||||
attrs={
|
||||
"placeholder": "Password",
|
||||
"class": "verify form-field submit-form",
|
||||
}
|
||||
),
|
||||
"password2": forms.PasswordInput(
|
||||
attrs={
|
||||
"placeholder": "Confirm password",
|
||||
"class": "verify form-field submit-form",
|
||||
}
|
||||
),
|
||||
}
|
||||
labels = {
|
||||
"username": "",
|
||||
"email": "",
|
||||
"password1": "",
|
||||
"password2": "",
|
||||
}
|
||||
|
||||
|
||||
class UserChangeForm(BaseUserChangeForm):
|
||||
class Meta(BaseUserChangeForm.Meta):
|
||||
model = User
|
||||
fields = (
|
||||
"username",
|
||||
"email",
|
||||
"first_name",
|
||||
"last_name",
|
||||
)
|
||||
widgets = {
|
||||
"username": forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": "Username",
|
||||
"class": "form-field submit-form",
|
||||
}
|
||||
),
|
||||
"email": forms.EmailInput(
|
||||
attrs={
|
||||
"placeholder": "Email",
|
||||
"class": "form-field submit-form",
|
||||
}
|
||||
),
|
||||
"first_name": forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": "First Name",
|
||||
"class": "form-field submit-form",
|
||||
}
|
||||
),
|
||||
"last_name": forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": "Last Name",
|
||||
"class": "form-field submit-form",
|
||||
}
|
||||
),
|
||||
}
|
||||
labels = {
|
||||
"username": "",
|
||||
"email": "",
|
||||
"first_name": "",
|
||||
"last_name": "",
|
||||
}
|
||||
|
||||
help_text = {
|
||||
"username": None,
|
||||
"email": None,
|
||||
"first_name": None,
|
||||
"last_name": None,
|
||||
}
|
||||
|
||||
|
||||
class ProfileForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Profile
|
||||
fields = ("picture",)
|
||||
labels = {
|
||||
"picture": "",
|
||||
}
|
||||
|
||||
help_text = {
|
||||
"picture": None,
|
||||
}
|
||||
|
||||
@ -8,25 +8,13 @@
|
||||
<form method="post"
|
||||
class="flex flex-col items-center justify-center space-y-4 my-auto">
|
||||
{% csrf_token %}
|
||||
<input type="text"
|
||||
placeholder="Username"
|
||||
name="username"
|
||||
value="{{ form.username.value|default:'' }}"
|
||||
class="verify mt-1 block rounded-md border-2 border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-lightsteelblue-100 focus:border-cadetblue-300" />
|
||||
{% if form.username.errors %}<p class="text-indianred-100 text-xs italic">{{ form.username.errors }}</p>{% endif %}
|
||||
<input type="password"
|
||||
placeholder="Password"
|
||||
name="password"
|
||||
value="{{ form.username.value|default:'' }}"
|
||||
class="verify mt-1 block rounded-md border-2 border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-lightsteelblue-100 focus:border-cadetblue-300" />
|
||||
{% if form.password.errors %}<p class="text-indianred-100 text-xs italic">{{ form.password.errors }}</p>{% endif %}
|
||||
{{ form }}
|
||||
<button type="submit"
|
||||
id="submit-button"
|
||||
class="px-12 py-2 border border-transparent rounded-md text-lightsteelblue-100 bg-slategray-200 opacity-50 transform ease-in-out duration-500 cursor-default">
|
||||
Login
|
||||
</button>
|
||||
class="button submit-button-disabled text-lightsteelblue-100 bg-slategray-200">Login</button>
|
||||
<p>
|
||||
Don't have an account? Create one <a class="underline text-skyblue-300" href="{% url 'signup' %}">Here</a>!
|
||||
Don't have an account? Create one <a class="underline text-skyblue-300 hover:text-cadetblue-300 transform duration-300 ease-linear"
|
||||
href="{% url 'signup' %}">Here</a>!
|
||||
</p>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
16
src/apps/account/templates/password.html
Normal file
16
src/apps/account/templates/password.html
Normal file
@ -0,0 +1,16 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block meta %}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="py-8 px-32">
|
||||
<form method="POST"
|
||||
enctype="multipart/form-data"
|
||||
class="flex flex-col items-center justify-center space-y-4 my-auto">
|
||||
<h2 class="text-3xl font-abel">Change password</h2>
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<button type="submit" class="button bg-skyblue-300 text-gray-500">Change</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -3,8 +3,28 @@
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block meta %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{{ user.username }}</h1>
|
||||
<img src="{{ user.profile_picture.url }}"
|
||||
alt="{{ user.username }}’s profile picture" />
|
||||
<p>{{ user.email }}</p>
|
||||
<div class="py-8 px-32">
|
||||
<div class="flex justify-end gap-4">
|
||||
<a href="{% url 'logout' %}">
|
||||
<i class="fa-solid fa-right-from-bracket fa-2xl"></i>
|
||||
</a>
|
||||
<a href="{% url 'settings' %}">
|
||||
<i class="fa-solid fa-gear fa-2xl"></i>
|
||||
</a>
|
||||
</div>
|
||||
<h1 class="font-abel text-4xl mb-8">My Projects</h1>
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
{% for project in projects %}
|
||||
<div class="border border-steelblue-100 p-8 flex flex-row gap-4 justify-between">
|
||||
<a href="{{ project.get_absolute_url }}"
|
||||
class="hover:text-steelblue-100 transition duration-300 ease-linear">
|
||||
<h2 class="font-abel text-3xl">{{ project.name }}</h2>
|
||||
</a>
|
||||
<p class="max-w-2xl text-justify">{{ project.description|slice:500 }}</p>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p class="text-center font-abel text-xl">No projects yet.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
24
src/apps/account/templates/setting.html
Normal file
24
src/apps/account/templates/setting.html
Normal file
@ -0,0 +1,24 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block meta %}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="py-8 px-32">
|
||||
<div class="flex justify-end">
|
||||
<a href="{% url 'change_password' %}">
|
||||
<i class="fa-solid fa-shield fa-2xl"></i>
|
||||
</a>
|
||||
</div>
|
||||
<form method="POST"
|
||||
enctype="multipart/form-data"
|
||||
class="flex flex-col items-center justify-center space-y-4 my-auto">
|
||||
<h2 class="text-3xl font-abel">Edit profile</h2>
|
||||
{% csrf_token %}
|
||||
{{ user_form.username }}
|
||||
{{ user_form.email }}
|
||||
{{ user_form.first_name }}
|
||||
{{ user_form.last_name }}
|
||||
<button type="submit" class="button bg-skyblue-300 text-gray-500">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -13,33 +13,31 @@
|
||||
placeholder="Username"
|
||||
name="username"
|
||||
value="{{ form.username.value|default:'' }}"
|
||||
class="verify mt-1 block rounded-md border-2 border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-lightsteelblue-100 focus:border-cadetblue-300" />
|
||||
class="verify form-field submit-form" />
|
||||
{% if form.username.errors %}<p class="text-indianred-100 text-xs italic">{{ form.username.errors }}</p>{% endif %}
|
||||
<input type="email"
|
||||
placeholder="Email (optional)"
|
||||
name="email"
|
||||
value="{{ form.email.value|default:'' }}"
|
||||
class="mt-1 block rounded-md border-2 border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-lightsteelblue-100 focus:border-cadetblue-300" />
|
||||
class="form-field submit-form" />
|
||||
{% if form.email.errors %}<p class="text-indianred-100 text-xs italic">{{ form.email.errors }}</p>{% endif %}
|
||||
<input type="password"
|
||||
placeholder="Password"
|
||||
name="password1"
|
||||
value="{{ form.password1.value|default:'' }}"
|
||||
class="verify mt-1 block rounded-md border-2 border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-lightsteelblue-100 focus:border-cadetblue-300" />
|
||||
class="verify form-field submit-form" />
|
||||
{% if form.password1.errors %}<p class="text-indianred-100 text-xs italic">{{ form.password1.errors }}</p>{% endif %}
|
||||
<input type="password"
|
||||
placeholder="Confirm password"
|
||||
name="password2"
|
||||
value="{{ form.password2.value|default:'' }}"
|
||||
class="verify mt-1 block rounded-md border-2 border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-lightsteelblue-100 focus:border-cadetblue-300" />
|
||||
class="verify form-field submit-form" />
|
||||
{% if form.password2.errors %}<p class="text-indianred-100 text-xs italic">{{ form.password2.errors }}</p>{% endif %}
|
||||
<button type="submit"
|
||||
id="submit-button"
|
||||
class="px-12 py-2 border border-transparent rounded-md text-lightsteelblue-100 bg-slategray-200 opacity-50 transform ease-in-out duration-500 cursor-default">
|
||||
Sign Up
|
||||
</button>
|
||||
class="button submit-button-disabled text-lightsteelblue-100 bg-slategray-200" />Sign Up</button>
|
||||
<p>
|
||||
Have an account? Login <a class="underline text-skyblue-300" href="{% url 'login' %}">Here</a>!
|
||||
</p>
|
||||
</form>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
@ -5,5 +5,7 @@ from . import views
|
||||
urlpatterns = [
|
||||
path("signup/", views.signup_view, name="signup"),
|
||||
path("login/", views.login_view, name="login"),
|
||||
path("<str:username>/", views.profile, name="profile"),
|
||||
path("logout/", views.LogoutView.as_view(), name="logout"),
|
||||
path("settings/", views.ProfileUpdateView.as_view(), name="settings"),
|
||||
path("settings/security/", views.PasswordChangeView.as_view(), name="change_password"),
|
||||
]
|
||||
|
||||
@ -1,19 +1,82 @@
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.contrib.auth.forms import AuthenticationForm
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import authenticate, login, logout, update_session_auth_hash
|
||||
from django.contrib.auth.forms import PasswordChangeForm
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.shortcuts import redirect, render
|
||||
from django.views.generic import ListView, TemplateView, View
|
||||
|
||||
from .forms import SignUpForm
|
||||
from .models import User
|
||||
from fossdb.models import Project
|
||||
|
||||
from .forms import LoginForm, SignUpForm, UserChangeForm
|
||||
|
||||
|
||||
def profile(request, username):
|
||||
user = get_object_or_404(User, username=username)
|
||||
class ProfileUpdateView(LoginRequiredMixin, TemplateView):
|
||||
template_name = "setting.html"
|
||||
login_url = "/login/"
|
||||
redirect_field_name = "redirect_to"
|
||||
|
||||
def get(self, request):
|
||||
user_form = UserChangeForm(instance=request.user)
|
||||
context = {
|
||||
"title": "Your profile",
|
||||
"user_form": user_form,
|
||||
}
|
||||
return self.render_to_response(context)
|
||||
|
||||
def post(self, request):
|
||||
user_form = UserChangeForm(request.POST, instance=request.user)
|
||||
|
||||
if user_form.is_valid():
|
||||
user_form.save()
|
||||
messages.add_message(request, messages.SUCCESS, "Your profile was successfully updated!")
|
||||
|
||||
context = {
|
||||
"title": user.username + ("" if not user.full_name else f" ({user.full_name})"),
|
||||
"user": user,
|
||||
"title": "Your profile",
|
||||
"user_form": user_form,
|
||||
}
|
||||
return render(request, "profile.html", context)
|
||||
return self.render_to_response(context)
|
||||
|
||||
|
||||
class PasswordChangeView(LoginRequiredMixin, TemplateView):
|
||||
template_name = "password.html"
|
||||
|
||||
def get(self, request):
|
||||
form = PasswordChangeForm(user=request.user)
|
||||
context = {
|
||||
"title": "Change password",
|
||||
"form": form,
|
||||
}
|
||||
return self.render_to_response(context)
|
||||
|
||||
def post(self, request):
|
||||
form = PasswordChangeForm(data=request.POST, user=request.user)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
|
||||
update_session_auth_hash(request, form.user)
|
||||
messages.add_message(request, messages.SUCCESS, "Your password was successfully updated!")
|
||||
|
||||
context = {
|
||||
"title": "Change password",
|
||||
"form": form,
|
||||
}
|
||||
return self.render_to_response(context)
|
||||
|
||||
|
||||
class ProfileProjectListView(LoginRequiredMixin, ListView):
|
||||
model = Project
|
||||
template_name = "profile.html"
|
||||
context_object_name = "projects"
|
||||
login_url = "/login/"
|
||||
redirect_field_name = "redirect_to"
|
||||
|
||||
def get_queryset(self):
|
||||
return Project.objects.filter(owner=self.request.user)
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
data = super().get_context_data(**kwargs)
|
||||
data["title"] = self.request.user.username + ("" if not self.request.user.full_name else f" ({self.request.user.full_name})")
|
||||
return data
|
||||
|
||||
|
||||
def signup_view(request):
|
||||
@ -34,7 +97,7 @@ def signup_view(request):
|
||||
|
||||
|
||||
def login_view(request):
|
||||
form = AuthenticationForm(data=request.POST or None)
|
||||
form = LoginForm(data=request.POST or None)
|
||||
if request.method == "POST":
|
||||
if form.is_valid():
|
||||
user = form.get_user()
|
||||
@ -46,3 +109,9 @@ def login_view(request):
|
||||
"form": form,
|
||||
}
|
||||
return render(request, "login.html", context)
|
||||
|
||||
|
||||
class LogoutView(View):
|
||||
def get(self, request):
|
||||
logout(request)
|
||||
return redirect("login")
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
import django_filters
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from .models import Project
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class UserFilter(django_filters.FilterSet):
|
||||
username = django_filters.CharFilter(lookup_expr="icontains")
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ("username",)
|
||||
|
||||
|
||||
class ProjectFilter(django_filters.FilterSet):
|
||||
owner = UserFilter()
|
||||
name = django_filters.CharFilter(lookup_expr="icontains")
|
||||
description = django_filters.CharFilter(lookup_expr="icontains")
|
||||
|
||||
class Meta:
|
||||
model = Project
|
||||
fields = (
|
||||
"owner",
|
||||
"name",
|
||||
"description",
|
||||
)
|
||||
@ -1,19 +1,33 @@
|
||||
from django import forms
|
||||
from django.forms import inlineformset_factory
|
||||
|
||||
from .models import HostingPlatform, ProgrammingLanguage, Project, ProjectHostingPlatform, ProjectProgrammingLanguage
|
||||
from .models import HostingPlatform, License, OperatingSystemVersion, ProgrammingLanguage, Project, ProjectHostingPlatform, ProjectProgrammingLanguage, Tag
|
||||
|
||||
|
||||
class HostingPlatformForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = ProjectHostingPlatform
|
||||
fields = (
|
||||
"url",
|
||||
"hosting_platform",
|
||||
"url",
|
||||
)
|
||||
widgets = {
|
||||
"hosting_platform": forms.Select(
|
||||
choices=HostingPlatform.objects.all(),
|
||||
)
|
||||
attrs={
|
||||
"class": "form-field submit-form",
|
||||
},
|
||||
),
|
||||
"url": forms.URLInput(
|
||||
attrs={
|
||||
"placeholder": "url",
|
||||
"class": "form-field submit-form font-roboto",
|
||||
},
|
||||
),
|
||||
}
|
||||
labels = {
|
||||
"hosting_platform": "",
|
||||
"url": "",
|
||||
}
|
||||
|
||||
|
||||
@ -27,14 +41,32 @@ class ProgrammingLanguageForm(forms.ModelForm):
|
||||
widgets = {
|
||||
"programming_language": forms.Select(
|
||||
choices=ProgrammingLanguage.objects.all(),
|
||||
attrs={
|
||||
"class": "form-field submit-form",
|
||||
},
|
||||
),
|
||||
"percentage": forms.NumberInput(
|
||||
attrs={
|
||||
"placeholder": "Percentage",
|
||||
"class": "form-field submit-form",
|
||||
"min": "0",
|
||||
"max": "100",
|
||||
}
|
||||
},
|
||||
),
|
||||
}
|
||||
labels = {
|
||||
"programming_language": "",
|
||||
"percentage": "",
|
||||
}
|
||||
|
||||
|
||||
ProgrammingLanguageInlineFormSet = inlineformset_factory(
|
||||
Project,
|
||||
ProjectProgrammingLanguage,
|
||||
form=ProgrammingLanguageForm,
|
||||
extra=0,
|
||||
can_delete=True,
|
||||
)
|
||||
|
||||
|
||||
class ProjectForm(forms.ModelForm):
|
||||
@ -51,17 +83,39 @@ class ProjectForm(forms.ModelForm):
|
||||
widgets = {
|
||||
"name": forms.TextInput(
|
||||
attrs={
|
||||
"class": "form-control",
|
||||
"placeholder": "Project name",
|
||||
}
|
||||
"class": "form-field submit-form font-roboto",
|
||||
},
|
||||
),
|
||||
"description": forms.Textarea(
|
||||
attrs={
|
||||
"class": "form-control",
|
||||
"placeholder": "Description",
|
||||
}
|
||||
"class": "form-field submit-form font-roboto",
|
||||
},
|
||||
),
|
||||
"license": forms.CheckboxSelectMultiple(
|
||||
choices=License.objects.all(),
|
||||
attrs={
|
||||
"class": "checkbox-form",
|
||||
},
|
||||
),
|
||||
"operating_system": forms.CheckboxSelectMultiple(
|
||||
choices=OperatingSystemVersion.objects.all(),
|
||||
attrs={
|
||||
"class": "checkbox-form",
|
||||
},
|
||||
),
|
||||
"tag": forms.CheckboxSelectMultiple(
|
||||
choices=Tag.objects.all(),
|
||||
attrs={
|
||||
"class": "checkbox-form",
|
||||
},
|
||||
),
|
||||
"license": forms.CheckboxSelectMultiple(),
|
||||
"tag": forms.CheckboxSelectMultiple(),
|
||||
"operating_system": forms.CheckboxSelectMultiple(),
|
||||
}
|
||||
labels = {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"license": "",
|
||||
"tag": "",
|
||||
"operating_system": "",
|
||||
}
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.2 on 2023-06-29 14:29
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fossdb', '0005_operatingsystem_is_linux'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='projectprogramminglanguage',
|
||||
name='percentage',
|
||||
field=models.PositiveIntegerField(blank=True, default=0),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,43 @@
|
||||
# Generated by Django 4.2.2 on 2023-06-29 15:06
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fossdb', '0006_alter_projectprogramminglanguage_percentage'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='license',
|
||||
name='full_name',
|
||||
field=models.CharField(db_index=True, max_length=100, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='license',
|
||||
name='short_name',
|
||||
field=models.CharField(db_index=True, max_length=50, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='operatingsystem',
|
||||
name='name',
|
||||
field=models.CharField(db_index=True, max_length=100, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='programminglanguage',
|
||||
name='name',
|
||||
field=models.CharField(db_index=True, max_length=100, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='description',
|
||||
field=models.TextField(blank=True, db_index=True, default=''),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='name',
|
||||
field=models.CharField(db_index=True, max_length=255),
|
||||
),
|
||||
]
|
||||
18
src/apps/fossdb/migrations/0008_alter_project_description.py
Normal file
18
src/apps/fossdb/migrations/0008_alter_project_description.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.2 on 2023-06-29 15:07
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fossdb', '0007_alter_license_full_name_alter_license_short_name_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='description',
|
||||
field=models.TextField(blank=True, default=''),
|
||||
),
|
||||
]
|
||||
16
src/apps/fossdb/migrations/0009_delete_star.py
Normal file
16
src/apps/fossdb/migrations/0009_delete_star.py
Normal file
@ -0,0 +1,16 @@
|
||||
# Generated by Django 4.2.2 on 2023-06-29 16:51
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fossdb', '0008_alter_project_description'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='Star',
|
||||
),
|
||||
]
|
||||
@ -6,8 +6,8 @@ from django.db import models
|
||||
|
||||
|
||||
class License(models.Model):
|
||||
short_name = models.CharField(max_length=50, unique=True)
|
||||
full_name = models.CharField(max_length=100, unique=True)
|
||||
short_name = models.CharField(max_length=50, unique=True, db_index=True)
|
||||
full_name = models.CharField(max_length=100, unique=True, db_index=True)
|
||||
url = models.URLField(blank=True, default="")
|
||||
text = models.TextField(blank=True, default="")
|
||||
|
||||
@ -16,7 +16,7 @@ class License(models.Model):
|
||||
|
||||
|
||||
class OperatingSystem(models.Model):
|
||||
name = models.CharField(max_length=100, unique=True)
|
||||
name = models.CharField(max_length=100, unique=True, db_index=True)
|
||||
description = models.TextField(blank=True, default="")
|
||||
is_linux = models.BooleanField(blank=True, default=False)
|
||||
|
||||
@ -55,7 +55,7 @@ class Tag(models.Model):
|
||||
|
||||
|
||||
class ProgrammingLanguage(models.Model):
|
||||
name = models.CharField(max_length=100, unique=True)
|
||||
name = models.CharField(max_length=100, unique=True, db_index=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@ -64,7 +64,7 @@ class ProgrammingLanguage(models.Model):
|
||||
class Project(models.Model):
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, verbose_name="ID")
|
||||
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=255)
|
||||
name = models.CharField(max_length=255, db_index=True)
|
||||
description = models.TextField(blank=True, default="")
|
||||
license = models.ManyToManyField(License, blank=True)
|
||||
tag = models.ManyToManyField(Tag, blank=True)
|
||||
@ -72,10 +72,6 @@ class Project(models.Model):
|
||||
programming_language = models.ManyToManyField(ProgrammingLanguage, through="ProjectProgrammingLanguage", blank=True)
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
@property
|
||||
def star_amount(self):
|
||||
return self.star.count()
|
||||
|
||||
@property
|
||||
def runs_on_macos(self):
|
||||
return self.operating_system.filter(operating_system__name="macOS").exists()
|
||||
@ -117,7 +113,7 @@ class Project(models.Model):
|
||||
class ProjectProgrammingLanguage(models.Model):
|
||||
project = models.ForeignKey(Project, on_delete=models.CASCADE)
|
||||
programming_language = models.ForeignKey(ProgrammingLanguage, on_delete=models.CASCADE)
|
||||
percentage = models.PositiveIntegerField(blank=True, null=True)
|
||||
percentage = models.PositiveIntegerField(blank=True, default=0)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.project.owner}/{self.project.name} | {self.programming_language} | {self.percentage}%"
|
||||
@ -137,8 +133,3 @@ class ProjectHostingPlatform(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
return self.url
|
||||
|
||||
|
||||
class Star(models.Model):
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
||||
project = models.ForeignKey(Project, on_delete=models.CASCADE)
|
||||
|
||||
@ -3,32 +3,62 @@
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block meta %}{% endblock %}
|
||||
{% block content %}
|
||||
<form method="post" id="project-form">
|
||||
<form class="flex flex-col items-center justify-center space-y-4 my-auto font-condensed"
|
||||
method="post"
|
||||
id="project-form">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
{{ hosting_platform.management_form }}
|
||||
{{ hosting_platform.as_table }}
|
||||
<div id="language-formset">
|
||||
{{ programming_language.management_form }}
|
||||
{% for form in programming_language %}<div class="language-form">{{ form.as_table }}</div>{% endfor %}
|
||||
{{ form.name }}
|
||||
{{ form.description }}
|
||||
<div class="flex gap-4 justify-center">
|
||||
<div class="">
|
||||
<p class="text-xl font-abel text-center">Licenses</p>
|
||||
{{ form.license }}
|
||||
</div>
|
||||
<!-- This button will trigger the JS to append another language form -->
|
||||
<button type="button" id="add-more">+</button>
|
||||
<!-- Render the empty form, which you'll use as a template for new entries -->
|
||||
<!-- Wrap it in a container so you can reference it by id and hide it -->
|
||||
<div id="empty-form" style="display:none;">{{ empty_form.as_table }}</div>
|
||||
<button type="submit">Submit</button>
|
||||
<div>
|
||||
<p class="text-xl font-abel text-center">Tags</p>
|
||||
{{ form.tag }}
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xl font-abel text-center">Operating systems</p>
|
||||
{{ form.operating_system }}
|
||||
</div>
|
||||
</div>
|
||||
{{ hosting_platform.management_form }}
|
||||
<div class="flex gap-4 items-center">
|
||||
<p class="text-xl font-abel">Hosting platform:</p>
|
||||
{{ hosting_platform }}
|
||||
</div>
|
||||
{{ programming_languages.management_form }}
|
||||
<!-- Languages container -->
|
||||
<div class="" id="languages-container">
|
||||
{% for language in programming_languages %}<div class="flex gap-4 items-center">{{ language }}</div>{% endfor %}
|
||||
</div>
|
||||
<!-- Hidden empty form used as a template -->
|
||||
<div id="empty-form" style="display:none">
|
||||
<div class="flex gap-4 items-center">{{ programming_languages.empty_form }}</div>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<div class="p-2 text-mediumaquamarine cursor-pointer hover:opacity-50 transition duration-100 ease-linear"
|
||||
id="add-language">
|
||||
<i class="fa-solid fa-plus fa-2xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<button class="button submit-button-enabled bg-mediumaquamarine text-gray-500"
|
||||
ype="submit">Save</button>
|
||||
</form>
|
||||
<script>
|
||||
document.querySelector("#add-more").addEventListener("click", function() {
|
||||
var formIndex = document.querySelector("#id_language-TOTAL_FORMS").value;
|
||||
var emptyFormDiv = document.querySelector("#empty-form");
|
||||
var newFormHTML = emptyFormDiv.innerHTML.replace(/__prefix__/g, formIndex);
|
||||
var newFormDiv = document.createElement("div");
|
||||
newFormDiv.className = "language-form";
|
||||
newFormDiv.innerHTML = newFormHTML;
|
||||
document.querySelector("#language-formset").append(newFormDiv);
|
||||
document.querySelector("#id_language-TOTAL_FORMS").value = parseInt(formIndex) + 1;
|
||||
});
|
||||
<script type="text/javascript">
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
document.getElementById("add-language").addEventListener("click", () => {
|
||||
var formIndex = document.getElementById("id_projectprogramminglanguage_set-TOTAL_FORMS");
|
||||
var emptyForm = document.getElementById("empty-form").innerHTML;
|
||||
|
||||
emptyForm = emptyForm.replace(/__prefix__/g, formIndex.value);
|
||||
|
||||
document.getElementById("languages-container")
|
||||
.insertAdjacentHTML("beforeend", emptyForm);
|
||||
|
||||
formIndex.value = parseInt(formIndex.value) + 1;
|
||||
})
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% block title %}Delete {{ project }}{% endblock %}
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block content %}
|
||||
<form method="post"
|
||||
id="delete-form"
|
||||
@ -9,9 +9,9 @@
|
||||
{% csrf_token %}
|
||||
<input type="text"
|
||||
id="confirm-input"
|
||||
class="mt-1 block rounded-md border-2 border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-lightsteelblue-100 focus:border-cadetblue-300 text-center" />
|
||||
class="text-center form-field border-slategray-200 hover:border-lightcoral bg-gray-300 focus:border-indianred-100 transition ease-linear" />
|
||||
<button id="submit-button"
|
||||
class="px-12 py-2 border border-transparent rounded-md text-lightsteelblue-100 font-bold uppercase bg-slategray-200 opacity-50 transform ease-in-out duration-500 cursor-default"
|
||||
class="button submit-button-disabled text-lightsteelblue-100 bg-slategray-200 uppercase font-bold"
|
||||
type="submit">Delete</button>
|
||||
</form>
|
||||
<script type="text/javascript">
|
||||
@ -23,30 +23,26 @@
|
||||
USER_INPUT.addEventListener("input", () => {
|
||||
if (confirm_string == USER_INPUT.value) {
|
||||
SUBMIT_BUTTON.classList.remove(
|
||||
"bg-slategray-200",
|
||||
"submit-button-disabled",
|
||||
"text-lightsteelblue-100",
|
||||
"opacity-50",
|
||||
"cursor-default"
|
||||
"bg-slategray-200"
|
||||
)
|
||||
SUBMIT_BUTTON.classList.add(
|
||||
"bg-indianred-100",
|
||||
"submit-button-enabled",
|
||||
"text-gray-500",
|
||||
"opacity-100",
|
||||
"hover:opacity-60"
|
||||
"bg-indianred-100",
|
||||
)
|
||||
SUBMIT_BUTTON.disabled = false
|
||||
} else {
|
||||
SUBMIT_BUTTON.classList.remove(
|
||||
"bg-indianred-100",
|
||||
"submit-button-enabled",
|
||||
"text-gray-500",
|
||||
"opacity-100",
|
||||
"hover:opacity-60"
|
||||
"bg-indianred-100",
|
||||
)
|
||||
SUBMIT_BUTTON.classList.add(
|
||||
"bg-slategray-200",
|
||||
"submit-button-disabled",
|
||||
"text-lightsteelblue-100",
|
||||
"opacity-50",
|
||||
"cursor-default"
|
||||
"bg-slategray-200"
|
||||
)
|
||||
SUBMIT_BUTTON.disabled = true
|
||||
}
|
||||
|
||||
@ -13,45 +13,73 @@
|
||||
</h2>
|
||||
<!-- os platform icons -->
|
||||
<div class="my-4 flex justify-center items-center gap-x-4">
|
||||
<a href=""><i class="fa-brands fa-linux fa-lg hover:text-lightsteelblue-200 transform duration-300 ease-in-out
|
||||
<!-- linux -->
|
||||
<a href="/search/?q=linux">
|
||||
<span title="Linux"
|
||||
class="hover:text-lightsteelblue-200 transform duration-200 ease-linear
|
||||
{% if project.runs_on_linux %}
|
||||
text-lightsteelblue-100
|
||||
{% else %}
|
||||
text-slategray-200
|
||||
{% endif %}"></i></a>
|
||||
<a href=""><i class="fa-brands fa-windows fa-lg hover:text-lightsteelblue-200 transform duration-300 ease-in-out
|
||||
{% endif %}">
|
||||
<i class="fa-brands fa-linux fa-lg"></i>
|
||||
</span>
|
||||
</a>
|
||||
<!-- windows -->
|
||||
<a href="/search/?q=windows">
|
||||
<span title="Windows"
|
||||
class="hover:text-lightsteelblue-200 transform duration-200 ease-linear
|
||||
{% if project.runs_on_windows %}
|
||||
text-lightsteelblue-100
|
||||
{% else %}
|
||||
text-slategray-200
|
||||
{% endif %}"></i></a>
|
||||
<a href=""><i class="fa-brands fa-apple fa-lg hover:text-lightsteelblue-200 transform duration-300 ease-in-out
|
||||
{% endif %}">
|
||||
<i class="fa-brands fa-windows fa-lg"></i>
|
||||
</span>
|
||||
</a>
|
||||
<!-- macos -->
|
||||
<a href="/search/?q=macos">
|
||||
<span title="macOS"
|
||||
class="hover:text-lightsteelblue-200 transform duration-200 ease-linear
|
||||
{% if project.runs_on_macos %}
|
||||
text-lightsteelblue-100
|
||||
{% else %}
|
||||
text-slategray-200
|
||||
{% endif %}"></i></a>
|
||||
<a href=""><i id="ios"
|
||||
class="fa-brands fa-app-store-ios fa-lg hover:text-lightsteelblue-200 transform duration-300 ease-in-out
|
||||
{% endif %}">
|
||||
<i class="fa-brands fa-apple fa-lg"></i>
|
||||
</span>
|
||||
</a>
|
||||
<!-- ios -->
|
||||
<a href="/search/?q=ios">
|
||||
<span title="iOS"
|
||||
class="fa-lg hover:text-lightsteelblue-200 transform duration-200 ease-linear
|
||||
{% if project.runs_on_ios %}
|
||||
text-lightsteelblue-100
|
||||
{% else %}
|
||||
text-slategray-200
|
||||
{% endif %}"></i></a>
|
||||
<a href=""><i id="android"
|
||||
class="fa-brands fa-android fa-lg hover:text-lightsteelblue-200 transform duration-300 ease-in-out
|
||||
{% endif %}">
|
||||
<i class="fa-brands fa-app-store-ios"></i>
|
||||
</span>
|
||||
</a>
|
||||
<!-- android -->
|
||||
<a href="/search/?q=android">
|
||||
<span title="Android"
|
||||
class="hover:text-lightsteelblue-200 transform duration-200 ease-linear
|
||||
{% if project.runs_on_android %}
|
||||
text-lightsteelblue-100
|
||||
{% else %}
|
||||
text-slategray-200
|
||||
{% endif %}"></i></a>
|
||||
{% endif %}">
|
||||
<i class="fa-brands fa-android fa-lg"></i>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<!-- tags -->
|
||||
<div class="my-8 flex flex-wrap justify-center items-start gap-2">
|
||||
{% for tag in project.tag.all|dictsort:"name" %}
|
||||
<a href="">
|
||||
<a href="/search/?q={{ tag.name }}">
|
||||
<span title="{{ tag.description }}"
|
||||
class="bg-opacity-0 border rounded-xl border-slategray-200 px-3 text-xs min-w-16 hover:bg-steelblue-400 hover:bg-opacity-60 transform duration-300 ease-in-out">
|
||||
class="bg-opacity-0 border rounded-xl border-slategray-200 px-3 text-xs min-w-16 hover:bg-steelblue-400 hover:bg-opacity-60 transform duration-200 ease-linear">
|
||||
{{ tag }}
|
||||
</span>
|
||||
</a>
|
||||
@ -60,9 +88,9 @@
|
||||
<!-- programming languages -->
|
||||
<div class="my-8 flex flex-wrap justify-center items-start gap-2">
|
||||
{% for language in project.projectprogramminglanguage_set.all|dictsortreversed:"percentage" %}
|
||||
<a href="">
|
||||
<a href="/search/?q={{ language.name }}">
|
||||
<span title="{{ language.percentage }}%"
|
||||
class="bg-opacity-0 border rounded-xl border-slategray-200 px-3 text-xs min-w-16 hover:bg-steelblue-400 hover:bg-opacity-60 transform duration-300 ease-in-out">
|
||||
class="bg-opacity-0 border rounded-xl border-slategray-200 px-3 text-xs min-w-16 hover:bg-steelblue-400 hover:bg-opacity-60 transform duration-200 ease-linear">
|
||||
{{ language.programming_language }}
|
||||
</span>
|
||||
</a>
|
||||
@ -75,7 +103,7 @@
|
||||
<div class="flex justify-between w-full mt-4 font-abel">
|
||||
<!-- licenses -->
|
||||
<div>
|
||||
<button class="text-xl underline font-abel hover:text-lightsteelblue-200 transform duration-300 ease-in-out"
|
||||
<button class="text-xl underline font-abel hover:text-lightsteelblue-200 transform duration-200 ease-linear"
|
||||
id="menu-button"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="true">Licenses</button>
|
||||
@ -83,7 +111,7 @@
|
||||
<!-- hosting platform -->
|
||||
<div class="">
|
||||
<p>
|
||||
See project source code and read more <a class="underline text-skyblue-300 hover:text-cadetblue-300 transform duration-300 ease-in-out"
|
||||
See project source code and read more <a class="underline text-skyblue-300 hover:text-cadetblue-300 transform duration-200 ease-linear"
|
||||
href="{{ project.projecthostingplatform.url }}"
|
||||
target="_blank">here</a>!
|
||||
</p>
|
||||
@ -100,7 +128,7 @@
|
||||
{% for license in project.license.all|dictsort:"short_name" %}
|
||||
<a href="{{ license.url }}">
|
||||
<span title="{{ license.full_name }}"
|
||||
class="bg-opacity-0 border rounded-xl border-slategray-200 px-3 text-xs min-w-16 hover:bg-steelblue-400 hover:bg-opacity-60">
|
||||
class="bg-opacity-0 border rounded-xl border-slategray-200 px-3 text-xs min-w-16 hover:bg-steelblue-400 hover:bg-opacity-60 transform duration-200 ease-linear">
|
||||
{{ license }}
|
||||
</span>
|
||||
</a>
|
||||
@ -111,12 +139,12 @@
|
||||
</div>
|
||||
{% if user == project.owner %}
|
||||
<div class="flex justify-between mt-8 mx-16">
|
||||
<button class="px-12 py-2 border border-transparent rounded-md text-gray-500 bg-skyblue-300 hover:opacity-60 transform ease-in-out duration-500">
|
||||
<a href="{% url 'project-update' project.owner project.name %}">Update</a>
|
||||
</button>
|
||||
<button class="px-12 py-2 border border-transparent rounded-md text-gray-500 bg-burlywood hover:opacity-60 transform ease-in-out duration-500">
|
||||
<a href="{% url 'project-delete' project.owner project.name %}">Delete</a>
|
||||
</button>
|
||||
<a href="{% url 'project-update' project.owner project.name %}">
|
||||
<button class="button hover:bg-opacity-60 bg-mediumpurple-100 text-gray-500">Update</button>
|
||||
</a>
|
||||
<a href="{% url 'project-delete' project.owner project.name %}">
|
||||
<button class="button hover:bg-opacity-60 bg-lightcoral text-gray-500">Delete</button>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@ -3,13 +3,20 @@
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block meta %}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="py-8 px-16">
|
||||
<h1 class="font-abel text-4xl mb-8">Projects</h1>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
{% for project in projects %}
|
||||
<div>
|
||||
<a href="{{ project.get_absolute_url }}">{{ project }}</a>
|
||||
<div class="border border-steelblue-100 p-8 flex flex-row gap-4 justify-between">
|
||||
<a href="{{ project.get_absolute_url }}"
|
||||
class="hover:text-steelblue-100 transition duration-300 ease-linear">
|
||||
<h2 class="font-abel text-2xl">{{ project.name }}</h2>
|
||||
</a>
|
||||
<p class="max-w-xs text-justify">{{ project.description|slice:500 }}</p>
|
||||
</div>
|
||||
<div>{{ project.description }}</div>
|
||||
<hr />
|
||||
{% empty %}
|
||||
<p>No projects found</p>
|
||||
<p class="text-center font-abel text-xl">No projects yet.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
22
src/apps/fossdb/templates/search.html
Normal file
22
src/apps/fossdb/templates/search.html
Normal file
@ -0,0 +1,22 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block meta %}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="py-8 px-32">
|
||||
<h1 class="font-abel text-4xl mb-8">Search Results</h1>
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
{% for project in projects %}
|
||||
<div class="border border-steelblue-100 p-8 flex flex-row gap-4 justify-between">
|
||||
<a href="{{ project.get_absolute_url }}"
|
||||
class="hover:text-steelblue-100 transition duration-300 ease-linear">
|
||||
<h2 class="font-abel text-3xl">{{ project.name }}</h2>
|
||||
</a>
|
||||
<p class="max-w-2xl text-justify">{{ project.description|slice:500 }}</p>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p class="text-center font-abel text-xl">No projects yet.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -3,9 +3,7 @@ from django.urls import path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path("explore/", views.ProjectListView.as_view(), name="explore"),
|
||||
path("contribute/", views.ProjectCreateView.as_view(), name="contribute"),
|
||||
path("<str:owner>/<str:project_name>/", views.ProjectDetailView.as_view(), name="project-detail"),
|
||||
path("<str:owner>/<str:project_name>/edit/", views.ProjectUpdateView.as_view(), name="project-update"),
|
||||
path("<str:owner>/<str:project_name>/delete/", views.ProjectDeleteView.as_view(), name="project-delete"),
|
||||
path("", views.ProjectDetailView.as_view(), name="project-detail"),
|
||||
path("edit/", views.ProjectUpdateView.as_view(), name="project-update"),
|
||||
path("delete/", views.ProjectDeleteView.as_view(), name="project-delete"),
|
||||
]
|
||||
|
||||
@ -1,64 +1,84 @@
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
||||
from django.forms import inlineformset_factory
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import redirect
|
||||
from django.views.generic import CreateView, DeleteView, DetailView, UpdateView
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
from django_filters.views import FilterView
|
||||
from django.views.generic import CreateView, DeleteView, DetailView, ListView, UpdateView
|
||||
|
||||
from .filters import ProjectFilter
|
||||
|
||||
from .forms import HostingPlatformForm, ProgrammingLanguageForm, ProjectForm
|
||||
from .models import Project, ProjectProgrammingLanguage
|
||||
|
||||
ProgrammingLanguageInlineFormset = inlineformset_factory(
|
||||
Project,
|
||||
ProjectProgrammingLanguage,
|
||||
form=ProgrammingLanguageForm,
|
||||
extra=1,
|
||||
)
|
||||
from .forms import HostingPlatformForm, ProgrammingLanguageInlineFormSet, ProjectForm
|
||||
from .models import Project
|
||||
|
||||
|
||||
class ProjectListView(FilterView):
|
||||
class SearchResultsListView(ListView):
|
||||
model = Project
|
||||
template_name = "search.html"
|
||||
context_object_name = "projects"
|
||||
|
||||
def get_queryset(self):
|
||||
query = self.request.GET.get("q")
|
||||
return Project.objects.filter(
|
||||
Q(owner__username__icontains=query)
|
||||
| Q(name__icontains=query)
|
||||
# | Q(description__icontains=query)
|
||||
| Q(license__short_name__icontains=query)
|
||||
| Q(license__full_name__icontains=query)
|
||||
| Q(tag__name__icontains=query)
|
||||
| Q(operating_system__operating_system__name__icontains=query)
|
||||
| Q(operating_system__codename__icontains=query)
|
||||
| Q(programming_language__name__icontains=query)
|
||||
).distinct()
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
data = super().get_context_data(**kwargs)
|
||||
data["title"] = "FOSSDB | Search"
|
||||
return data
|
||||
|
||||
|
||||
class ProjectListView(ListView):
|
||||
model = Project
|
||||
template_name = "explore.html"
|
||||
filterset_class = ProjectFilter
|
||||
context_object_name = "projects"
|
||||
paginate_by = 100 # optional 10 projects a page
|
||||
paginate_by = 50 # amount of items on screen
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
data = super().get_context_data(**kwargs)
|
||||
data["title"] = "FOSSDB | Explore"
|
||||
return data
|
||||
|
||||
|
||||
class ProjectCreateView(LoginRequiredMixin, CreateView):
|
||||
model = Project
|
||||
form_class = ProjectForm
|
||||
template_name = "create_view.html"
|
||||
login_url = "/login/"
|
||||
login_url = reverse_lazy("login")
|
||||
redirect_field_name = "redirect_to"
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
data = super().get_context_data(**kwargs)
|
||||
data["hosting_platform"] = HostingPlatformForm(self.request.POST or None, prefix="hosting")
|
||||
data["programming_language"] = ProgrammingLanguageInlineFormset(self.request.POST or None, prefix="language")
|
||||
data["empty_form"] = ProgrammingLanguageInlineFormset(prefix="language_empty")
|
||||
data["title"] = "FOSSDB | Create Project"
|
||||
data["hosting_platform"] = HostingPlatformForm(self.request.POST or None)
|
||||
data["programming_languages"] = ProgrammingLanguageInlineFormSet(self.request.POST or None)
|
||||
return data
|
||||
|
||||
def form_valid(self, form):
|
||||
context = self.get_context_data()
|
||||
|
||||
form.instance.owner = self.request.user
|
||||
|
||||
self.object = form.save() # Save project form
|
||||
|
||||
hosting_platform = context["hosting_platform"]
|
||||
programming_language = context["programming_language"]
|
||||
self.object = form.save()
|
||||
if hosting_platform.is_valid():
|
||||
hosting_platform.instance.project = self.object
|
||||
hosting_platform = hosting_platform.save(commit=False)
|
||||
hosting_platform.project = self.object
|
||||
hosting_platform.save()
|
||||
# TODO: allow adding multiple languages
|
||||
if programming_language.is_valid():
|
||||
for instance in programming_language.save(commit=False):
|
||||
instance.project = self.object
|
||||
instance.save()
|
||||
programming_language.save_m2m()
|
||||
if hosting_platform.is_valid() and programming_language.is_valid():
|
||||
|
||||
programming_languages = context["programming_languages"]
|
||||
if programming_languages.is_valid():
|
||||
programming_languages.instance = self.object
|
||||
programming_languages.save()
|
||||
|
||||
return super().form_valid(form)
|
||||
else:
|
||||
return self.render_to_response(self.get_context_data(form=form))
|
||||
|
||||
|
||||
class ProjectDetailView(DetailView):
|
||||
@ -75,7 +95,7 @@ class ProjectUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
|
||||
form_class = ProjectForm
|
||||
slug_field = "name"
|
||||
slug_url_kwarg = "project_name"
|
||||
login_url = "/login/"
|
||||
login_url = reverse_lazy("login")
|
||||
redirect_field_name = "redirect_to"
|
||||
|
||||
def test_func(self):
|
||||
@ -85,24 +105,48 @@ class ProjectUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
|
||||
return redirect("login")
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
data = super(ProjectUpdateView, self).get_context_data(**kwargs)
|
||||
data["hosting_platform"] = HostingPlatformForm(self.request.POST or None, instance=self.object.projecthostingplatform, prefix="hosting")
|
||||
data["programming_language"] = ProgrammingLanguageInlineFormset(self.request.POST or None, instance=self.object, prefix="language")
|
||||
data["empty_form"] = ProgrammingLanguageInlineFormset(prefix="language_empty")
|
||||
data = super().get_context_data(**kwargs)
|
||||
data["title"] = f"Edit {self.object}"
|
||||
data["hosting_platform"] = HostingPlatformForm(self.request.POST or None, instance=self.object.projecthostingplatform)
|
||||
data["programming_languages"] = ProgrammingLanguageInlineFormSet(self.request.POST or None, instance=self.object)
|
||||
return data
|
||||
|
||||
def form_valid(self, form):
|
||||
context = self.get_context_data()
|
||||
|
||||
form.instance.owner = self.request.user
|
||||
|
||||
self.object = form.save() # Save project form
|
||||
|
||||
hosting_platform = context["hosting_platform"]
|
||||
if hosting_platform.is_valid():
|
||||
hosting_platform.project = self.object
|
||||
hosting_platform.save()
|
||||
|
||||
programming_languages = context["programming_languages"]
|
||||
if programming_languages.is_valid():
|
||||
programming_languages.instance = self.object
|
||||
programming_languages.save()
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class ProjectDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
|
||||
model = Project
|
||||
template_name = "delete_view.html"
|
||||
slug_field = "name"
|
||||
slug_url_kwarg = "project_name"
|
||||
login_url = "/login/"
|
||||
login_url = reverse_lazy("login")
|
||||
redirect_field_name = "redirect_to"
|
||||
success_url = "/"
|
||||
success_url = reverse_lazy("explore")
|
||||
|
||||
def test_func(self):
|
||||
return self.get_object().owner == self.request.user
|
||||
|
||||
def handle_no_permission(self):
|
||||
return redirect("login")
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
data = super().get_context_data(**kwargs)
|
||||
data["title"] = f"Delete {self.object}"
|
||||
return data
|
||||
|
||||
5
src/apps/main/templates/dashboard.html
Normal file
5
src/apps/main/templates/dashboard.html
Normal file
@ -0,0 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block meta %}{% endblock %}
|
||||
{% block content %}{% endblock %}
|
||||
5
src/apps/main/templates/help.html
Normal file
5
src/apps/main/templates/help.html
Normal file
@ -0,0 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block meta %}{% endblock %}
|
||||
{% block content %}{% endblock %}
|
||||
5
src/apps/main/templates/news.html
Normal file
5
src/apps/main/templates/news.html
Normal file
@ -0,0 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block meta %}{% endblock %}
|
||||
{% block content %}{% endblock %}
|
||||
@ -1,11 +1,20 @@
|
||||
from account.views import ProfileProjectListView
|
||||
from django.urls import path
|
||||
from fossdb.views import ProjectCreateView, ProjectListView, SearchResultsListView
|
||||
|
||||
from . import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path("", views.homepage, name="homepage"),
|
||||
path("contribute/", views.contribute, name="contribute"),
|
||||
path("news/", views.news, name="news"),
|
||||
path("search/", SearchResultsListView.as_view(), name="search"),
|
||||
path("explore/", ProjectListView.as_view(), name="explore"),
|
||||
path("contribute/", ProjectCreateView.as_view(), name="contribute"),
|
||||
path("dashboard/", views.dashboard, name="dashboard"),
|
||||
path("news/", views.news, name="news"),
|
||||
path("help/", views.help, name="help"),
|
||||
path("login/", views.login),
|
||||
path("logout/", views.logout),
|
||||
path("signup/", views.signup),
|
||||
path("<str:username>/", ProfileProjectListView.as_view(), name="profile"),
|
||||
]
|
||||
|
||||
@ -1,10 +1,22 @@
|
||||
from django.shortcuts import render
|
||||
from django.shortcuts import redirect, render
|
||||
|
||||
|
||||
def homepage(request):
|
||||
return render(request, "homepage.html", {"title": "FOSSDB"})
|
||||
|
||||
|
||||
def login(request):
|
||||
return redirect("login")
|
||||
|
||||
|
||||
def logout(request):
|
||||
return redirect("logout")
|
||||
|
||||
|
||||
def signup(request):
|
||||
return redirect("signup")
|
||||
|
||||
|
||||
def contribute(request):
|
||||
return render(request, "contribute.html", {"title": "FOSSDB | Contribute"})
|
||||
|
||||
|
||||
@ -2,10 +2,6 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@import url("https://fonts.googleapis.com/css2?family=Abel&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Rationale&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap");
|
||||
|
||||
body {
|
||||
@apply leading-[normal] m-0;
|
||||
}
|
||||
@ -28,3 +24,27 @@ body {
|
||||
background: linear-gradient(to right, transparent, #27a1b9, transparent);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.form-field {
|
||||
@apply mt-1 block rounded-md border-2;
|
||||
}
|
||||
|
||||
.submit-form {
|
||||
@apply border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-slategray-100 focus:border-cadetblue-300;
|
||||
}
|
||||
|
||||
.checkbox-form {
|
||||
@apply border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-slategray-100 focus:border-cadetblue-300;
|
||||
}
|
||||
|
||||
.button {
|
||||
@apply font-condensed text-xl px-12 py-2 border rounded-md transform ease-linear duration-100;
|
||||
}
|
||||
|
||||
.submit-button-disabled {
|
||||
@apply opacity-60 cursor-default;
|
||||
}
|
||||
|
||||
.submit-button-enabled {
|
||||
@apply opacity-100 hover:opacity-60 hover:bg-opacity-60;
|
||||
}
|
||||
|
||||
@ -7,11 +7,11 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Abel&family=Rationale&family=Roboto+Condensed:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap"
|
||||
rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Abel&family=Roboto+Condensed:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
|
||||
<link href="https://fonts.googleapis.com/css2?family=Abel&display=swap"
|
||||
rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
|
||||
<link href="https://fonts.googleapis.com/css2?family=Rationale&display=swap"
|
||||
rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
|
||||
rel="stylesheet" />
|
||||
@ -27,32 +27,35 @@
|
||||
<header class="flex justify-between items-center px-6 py-2 font-abel text-xl">
|
||||
<div class="flex justify-between">
|
||||
<!-- logo -->
|
||||
<div class="flex items-center text-4xl font-abel">
|
||||
<div class="flex items-center text-4xl font-rationale">
|
||||
<img class=""
|
||||
width="40"
|
||||
src="{% static 'img/icons/logo.svg' %}"
|
||||
alt="logo" />
|
||||
<a class="hover:text-skyblue-300 transform duration-300 ease-in-out"
|
||||
<a class="hover:text-skyblue-300 transform duration-300 ease-linear"
|
||||
href="{% url 'homepage' %}">foss<span class="text-skyblue-300">db</span></a>
|
||||
</div>
|
||||
<!-- search -->
|
||||
<div class="relative items-center flex">
|
||||
<form action="{% url 'search' %}"
|
||||
method="get"
|
||||
class="relative items-center flex">
|
||||
<input type="text"
|
||||
name="q"
|
||||
placeholder="Search projects..."
|
||||
class="py-2 mx-4 placeholder-slategray-100 bg-gray-300 border-0 border-b-2 border-b-slategray-100 hover:border-b-lightsteelblue-100 transform duration-200 ease-in-out focus:outline-none focus:border-b-skyblue-300 focus:border-b-2 max-w-[10rem]" />
|
||||
</div>
|
||||
class="py-2 mx-4 placeholder-slategray-100 bg-gray-300 border-0 border-b-2 border-b-slategray-100 hover:border-b-lightsteelblue-100 transform duration-200 ease-linear focus:outline-none focus:border-b-skyblue-300 focus:border-b-2 max-w-[10rem]" />
|
||||
</form>
|
||||
<!-- navbar -->
|
||||
<nav class="uppercase flex gap-x-6 items-center">
|
||||
<a href="{% url 'explore' %}"
|
||||
class="hover:text-skyblue-300 transform duration-200 ease-in-out">explore</a>
|
||||
class="hover:text-skyblue-300 transform duration-200 ease-linear">explore</a>
|
||||
<a href="{% url 'contribute' %}"
|
||||
class="hover:text-skyblue-300 transform duration-200 ease-in-out">contribute</a>
|
||||
class="hover:text-skyblue-300 transform duration-200 ease-linear">contribute</a>
|
||||
<a href="{% url 'news' %}"
|
||||
class="hover:text-skyblue-300 transform duration-200 ease-in-out">news</a>
|
||||
class="hover:text-skyblue-300 transform duration-200 ease-linear">news</a>
|
||||
<a href="{% url 'dashboard' %}"
|
||||
class="hover:text-skyblue-300 transform duration-200 ease-in-out">dashboard</a>
|
||||
class="hover:text-skyblue-300 transform duration-200 ease-linear">dashboard</a>
|
||||
<a href="{% url 'help' %}"
|
||||
class="hover:text-skyblue-300 transform duration-200 ease-in-out">help</a>
|
||||
class="hover:text-skyblue-300 transform duration-200 ease-linear">help</a>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
@ -61,14 +64,12 @@
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="mx-4">{{ user.username }}</p>
|
||||
<a href="{% url 'profile' user.username %}">
|
||||
<img src="{{ user.profile.picture.url }}"
|
||||
class="w-[2rem]"
|
||||
alt="{{ user.username }}'s profile picture" />
|
||||
<img src="{{ user.profile.picture.url }}" class="w-[2rem]" alt="pic" />
|
||||
</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<a href="{% url 'login' %}"
|
||||
class="hover:text-skyblue-300 transition duration-300 ease-in-out"><i class="fa-solid fa-user fa-lg ml-4 "></i></a>
|
||||
class="hover:text-skyblue-300 transition duration-300 ease-linear"><i class="fa-solid fa-user fa-lg ml-4 "></i></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</header>
|
||||
@ -81,10 +82,9 @@
|
||||
<p>
|
||||
FOSSDB is a passion project of <a class="underline"
|
||||
href="https://github.com/kristoferssolo"
|
||||
target="_blank">@kristoferssolo</a> and a dedicated community of reporters.
|
||||
target="_blank">@kristoferssolo</a>.
|
||||
</p>
|
||||
<!-- TODO: finish these sentences -->
|
||||
<p>This site uses data from GitHub as well as data...</p>
|
||||
<p>This site has no affiliation with GitHub or any other hosting platform.</p>
|
||||
</div>
|
||||
<div></div>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 29 KiB |
@ -1,3 +1,11 @@
|
||||
Array.from(document.getElementsByClassName("remove-btn")).forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
console.log("TRUE")
|
||||
const hiddenDeleteInput = button.previousElementSibling
|
||||
hiddenDeleteInput.value = "on"
|
||||
button.parentElement.style.display = "none"
|
||||
})
|
||||
})
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
const FORM_VERIFY = document.getElementsByClassName("verify")
|
||||
const SUBMIT_BUTTON = document.getElementById("submit-button")
|
||||
@ -10,30 +18,26 @@ window.addEventListener("DOMContentLoaded", () => {
|
||||
)
|
||||
if (ALL_FILLED) {
|
||||
SUBMIT_BUTTON.classList.remove(
|
||||
"bg-slategray-200",
|
||||
"submit-button-disabled",
|
||||
"text-lightsteelblue-100",
|
||||
"opacity-50",
|
||||
"cursor-default"
|
||||
"bg-slategray-200"
|
||||
)
|
||||
SUBMIT_BUTTON.classList.add(
|
||||
"bg-skyblue-300",
|
||||
"submit-button-enabled",
|
||||
"text-gray-500",
|
||||
"opacity-100",
|
||||
"hover:opacity-60"
|
||||
"bg-skyblue-300"
|
||||
)
|
||||
SUBMIT_BUTTON.disabled = false
|
||||
} else {
|
||||
SUBMIT_BUTTON.classList.remove(
|
||||
"bg-skyblue-300",
|
||||
"submit-button-enabled",
|
||||
"text-gray-500",
|
||||
"opacity-100",
|
||||
"hover:opacity-60"
|
||||
"bg-skyblue-300"
|
||||
)
|
||||
SUBMIT_BUTTON.classList.add(
|
||||
"bg-slategray-200",
|
||||
"submit-button-disabled",
|
||||
"text-lightsteelblue-100",
|
||||
"opacity-50",
|
||||
"cursor-default"
|
||||
"bg-slategray-200"
|
||||
)
|
||||
SUBMIT_BUTTON.disabled = true
|
||||
}
|
||||
|
||||
24
tox.ini
24
tox.ini
@ -1,24 +0,0 @@
|
||||
[tox]
|
||||
minversion = 3.8.0
|
||||
envlist = django, flake8, mypy
|
||||
isolated_build = true
|
||||
|
||||
[gh-actions]
|
||||
python =
|
||||
3.10: py310, mypy, flake8
|
||||
|
||||
[testenv:django]
|
||||
basepython = python3.10
|
||||
deps = django
|
||||
commands = python manage.py test
|
||||
|
||||
[testenv:flake8]
|
||||
basepython = python3.10
|
||||
deps = flake8
|
||||
commands = flake8 FOSSDB_web
|
||||
|
||||
[testenv:mypy]
|
||||
basepython = python3.10
|
||||
deps =
|
||||
-r{toxinidir}/requirements_dev.txt
|
||||
commands = mypy FOSSDB_web
|
||||
Loading…
Reference in New Issue
Block a user