mirror of
https://github.com/wahyd4/links.git
synced 2026-08-08 21:04:53 +10:00
Add standalone search page
This commit is contained in:
@@ -74,3 +74,53 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License.
|
||||
|
||||
## Local Development
|
||||
|
||||
To run the URL Manager locally:
|
||||
|
||||
1. Clone the repository:
|
||||
```
|
||||
git clone https://github.com/yourusername/url-manager.git
|
||||
cd url-manager
|
||||
```
|
||||
|
||||
2. Create and activate a virtual environment:
|
||||
```
|
||||
python -m venv venv
|
||||
source venv/bin/activate # On Windows, use `venv\Scripts\activate`
|
||||
```
|
||||
|
||||
3. Install dependencies:
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
4. Apply database migrations:
|
||||
```
|
||||
python manage.py migrate
|
||||
```
|
||||
|
||||
5. Create a superuser:
|
||||
```
|
||||
python manage.py createsuperuser
|
||||
```
|
||||
|
||||
6. Compile translation messages:
|
||||
```
|
||||
python manage.py compilemessages
|
||||
```
|
||||
|
||||
7. In one terminal, start the Tailwind CSS build process:
|
||||
```
|
||||
python manage.py tailwind start
|
||||
```
|
||||
|
||||
8. In another terminal, run the Django development server:
|
||||
```
|
||||
python manage.py runserver
|
||||
```
|
||||
|
||||
9. Open your browser and go to `http://127.0.0.1:8000`
|
||||
|
||||
Remember to keep both the Tailwind CSS build process and the Django development server running while you're developing.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,43 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-4xl mx-auto mt-8">
|
||||
<div class="bg-white shadow-md rounded-lg overflow-hidden">
|
||||
<div class="px-6 py-4 bg-gray-50 border-b border-gray-200">
|
||||
<h1 class="text-2xl font-bold text-gray-800">{% trans "Search Links" %}</h1>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<form method="get" action="{% url 'search' %}" class="mb-6">
|
||||
<div class="flex">
|
||||
<input type="text" name="q" value="{{ query }}" class="flex-grow px-4 py-2 border rounded-l-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="{% trans 'Enter alias or URL' %}">
|
||||
<button type="submit" class="px-4 py-2 bg-blue-500 text-white rounded-r-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500">{% trans "Search" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% if query %}
|
||||
<h2 class="text-xl font-semibold mb-4">{% trans "Search Results for" %} "{{ query }}"</h2>
|
||||
{% if links %}
|
||||
<ul class="divide-y divide-gray-200">
|
||||
{% for link in links %}
|
||||
<li class="py-4">
|
||||
<div class="flex justify-between">
|
||||
<div>
|
||||
<a href="{% url 'redirect_to_original' link.alias %}" class="text-blue-600 hover:underline font-semibold">{{ link.alias }}</a>
|
||||
<p class="text-sm text-gray-600">{{ link.original_url }}</p>
|
||||
</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
{% trans "Clicks" %}: {{ link.click_count }}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="text-gray-600">{% trans "No results found." %}</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -8,5 +8,6 @@ urlpatterns = [
|
||||
path('delete/<int:pk>/', views.LinkDeleteView.as_view(), name='link_delete'),
|
||||
path('delete-selected/', views.delete_selected, name='delete_selected'),
|
||||
path('detail/<int:pk>/', views.LinkDetailView.as_view(), name='link_detail'),
|
||||
path('search/', views.SearchView.as_view(), name='search'),
|
||||
path('<str:alias>/', views.redirect_to_original, name='redirect_to_original'),
|
||||
]
|
||||
|
||||
+13
-1
@@ -1,5 +1,5 @@
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.views.generic import ListView, CreateView, UpdateView, DeleteView, DetailView
|
||||
from django.views.generic import ListView, CreateView, UpdateView, DeleteView, DetailView, TemplateView
|
||||
from django.urls import reverse_lazy, reverse # Add 'reverse' here
|
||||
from django.db.models import F, Count, Q
|
||||
from django.db.models.functions import TruncDate
|
||||
@@ -134,3 +134,15 @@ def search_aliases(request):
|
||||
|
||||
print(f"Search results: {results}") # Debug print
|
||||
return JsonResponse(results, safe=False)
|
||||
|
||||
class SearchView(TemplateView):
|
||||
template_name = 'links/search.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
query = self.request.GET.get('q', '')
|
||||
if query:
|
||||
links = Link.objects.filter(Q(alias__icontains=query) | Q(original_url__icontains=query))
|
||||
context['links'] = links
|
||||
context['query'] = query
|
||||
return context
|
||||
|
||||
Binary file not shown.
@@ -96,6 +96,24 @@ msgstr "编辑链接"
|
||||
msgid "Search aliases..."
|
||||
msgstr "搜索别名..."
|
||||
|
||||
msgid "Search Links"
|
||||
msgstr "搜索链接"
|
||||
|
||||
msgid "Enter alias or URL"
|
||||
msgstr "输入别名或URL"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "搜索"
|
||||
|
||||
msgid "Search Results for"
|
||||
msgstr "搜索结果:"
|
||||
|
||||
msgid "No results found."
|
||||
msgstr "未找到结果。"
|
||||
|
||||
msgid "Advanced Search"
|
||||
msgstr "高级搜索"
|
||||
|
||||
#: 3.12/lib/python3.12/site-packages/django/forms/fields.py:95
|
||||
msgid "This field is required."
|
||||
msgstr ""
|
||||
|
||||
Vendored
+43
@@ -888,6 +888,10 @@ select {
|
||||
margin-top: 5rem;
|
||||
}
|
||||
|
||||
.mb-6 {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
@@ -952,6 +956,10 @@ select {
|
||||
max-width: 56rem;
|
||||
}
|
||||
|
||||
.flex-grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.border-collapse {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
@@ -1045,6 +1053,16 @@ select {
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
.rounded-l-md {
|
||||
border-top-left-radius: 0.375rem;
|
||||
border-bottom-left-radius: 0.375rem;
|
||||
}
|
||||
|
||||
.rounded-r-md {
|
||||
border-top-right-radius: 0.375rem;
|
||||
border-bottom-right-radius: 0.375rem;
|
||||
}
|
||||
|
||||
.border {
|
||||
border-width: 1px;
|
||||
}
|
||||
@@ -1313,22 +1331,47 @@ select {
|
||||
color: rgb(127 29 29 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.hover\:text-gray-200:hover {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(229 231 235 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.hover\:underline:hover {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.focus\:border-blue-300:focus {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(147 197 253 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.focus\:outline-none:focus {
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.focus\:ring:focus {
|
||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
||||
}
|
||||
|
||||
.focus\:ring-2:focus {
|
||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
||||
}
|
||||
|
||||
.focus\:ring-blue-200:focus {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(191 219 254 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.focus\:ring-blue-500:focus {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.focus\:ring-opacity-50:focus {
|
||||
--tw-ring-opacity: 0.5;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
<div class="container mx-auto flex justify-between items-center">
|
||||
<a href="{% url 'link_list' %}" class="text-xl font-bold">{% trans "URL Manager" %}</a>
|
||||
<div class="flex items-center space-x-4">
|
||||
<a href="{% url 'search' %}" class="text-white hover:text-gray-200">{% trans "Advanced Search" %}</a>
|
||||
<div class="relative">
|
||||
<input type="text" id="search-input" class="bg-white text-gray-800 rounded px-4 py-2 w-64" placeholder="{% trans 'Search aliases...' %}">
|
||||
<div id="search-results" class="absolute top-full left-0 right-0 bg-white border border-gray-300 rounded mt-1 hidden"></div>
|
||||
|
||||
Binary file not shown.
@@ -58,7 +58,8 @@ ALLOWED_HOSTS = []
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': '/app/data/db.sqlite3', # Updated path
|
||||
# 'NAME': '/app/data/db.sqlite3', # Updated path
|
||||
'NAME': BASE_DIR / 'data/db.sqlite3',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user