from django.shortcuts import render, redirect, get_object_or_404 from django.views import View from django.views.generic import ListView, CreateView, DetailView, UpdateView, DeleteView from django.contrib.auth.mixins import LoginRequiredMixin from django.urls import reverse_lazy, reverse from django.db.models import Q from django.db import transaction from django.http import JsonResponse, HttpResponseRedirect from .models import Bookmark, Team, Extraction, BookmarkShare, Summary from .tasks import process_bookmark from auditlog.models import LogEntry class BookmarkListView(LoginRequiredMixin, ListView): model = Bookmark template_name = 'core/index.html' context_object_name = 'bookmarks' paginate_by = 20 def get_queryset(self): queryset = Bookmark.objects.filter(user=self.request.user).order_by('-created_at') # Search filter query = self.request.GET.get('q') if query: queryset = queryset.filter( Q(title__icontains=query) | Q(url__icontains=query) | Q(notes__icontains=query) | Q(extraction__content_text__icontains=query) ).distinct() # Tag filter tag = self.request.GET.get('tag') if tag: queryset = queryset.filter(tags__name__in=[tag]) return queryset def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Add all tags used by the user for a sidebar or filter list from taggit.models import Tag context['all_tags'] = Tag.objects.filter(bookmark__user=self.request.user).distinct() context['teams'] = self.request.user.teams.all() return context class BookmarkCreateView(LoginRequiredMixin, CreateView): model = Bookmark fields = ['url', 'title', 'notes', 'is_favorite'] template_name = 'core/bookmark_form.html' success_url = reverse_lazy('home') def form_valid(self, form): form.instance.user = self.request.user # Save first to get the object self.object = form.save() # Handle tags if provided in a separate field or as a comma-separated string tags = self.request.POST.get('tags_input') if tags: self.object.tags.add(*[t.strip() for t in tags.split(',')]) # Trigger background task process_bookmark.delay(self.object.id) if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest': return JsonResponse({'status': 'success', 'redirect_url': str(self.success_url)}) return HttpResponseRedirect(self.get_success_url()) class BookmarkUpdateView(LoginRequiredMixin, UpdateView): model = Bookmark fields = ['url', 'title', 'notes', 'is_favorite'] template_name = 'core/bookmark_form.html' def get_success_url(self): return reverse('bookmark-detail', kwargs={'pk': self.object.pk}) def form_valid(self, form): # Handle tags update tags_input = self.request.POST.get('tags_input', '') with transaction.atomic(): self.object = form.save() # Clear existing tags and set new ones self.object.tags.clear() if tags_input: tag_names = [t.strip() for t in tags_input.split(',') if t.strip()] if tag_names: self.object.tags.add(*tag_names) return HttpResponseRedirect(self.get_success_url()) def get_queryset(self): return Bookmark.objects.filter(user=self.request.user) class BookmarkDeleteView(LoginRequiredMixin, DeleteView): model = Bookmark success_url = reverse_lazy('home') def get_queryset(self): return Bookmark.objects.filter(user=self.request.user) class BookmarkDetailView(LoginRequiredMixin, DetailView): model = Bookmark template_name = 'core/bookmark_detail.html' context_object_name = 'bookmark' def get_queryset(self): # Allow viewing if it's the user's bookmark OR shared with one of their teams user_teams = self.request.user.teams.all() return Bookmark.objects.filter( Q(user=self.request.user) | Q(shares__team__in=user_teams) ).distinct() class BookmarkRegenerateView(LoginRequiredMixin, View): def post(self, request, pk): bookmark = get_object_or_404(Bookmark, pk=pk, user=request.user) # Delete existing summary and extraction to force regeneration and show loading states if hasattr(bookmark, 'summary'): bookmark.summary.delete() if hasattr(bookmark, 'extraction'): bookmark.extraction.delete() process_bookmark.delay(bookmark.id) return HttpResponseRedirect(reverse('bookmark-detail', args=[pk])) class SummaryUpdateView(LoginRequiredMixin, View): def post(self, request, pk): bookmark = get_object_or_404(Bookmark, pk=pk, user=request.user) content = request.POST.get('content') if content: Summary.objects.update_or_create( bookmark=bookmark, defaults={'content': content} ) return HttpResponseRedirect(reverse('bookmark-detail', args=[pk])) class ExtractionUpdateView(LoginRequiredMixin, View): def post(self, request, pk): bookmark = get_object_or_404(Bookmark, pk=pk, user=request.user) content_text = request.POST.get('content_text') if content_text: Extraction.objects.update_or_create( bookmark=bookmark, defaults={'content_text': content_text} ) return HttpResponseRedirect(reverse('bookmark-detail', args=[pk])) class TeamListView(LoginRequiredMixin, ListView): model = Team template_name = 'core/team_list.html' context_object_name = 'teams' def get_queryset(self): return self.request.user.teams.all() class TeamDetailView(LoginRequiredMixin, DetailView): model = Team template_name = 'core/team_detail.html' context_object_name = 'team' def get_queryset(self): return self.request.user.teams.all() def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Get bookmarks shared with this team context['shared_bookmarks'] = Bookmark.objects.filter(shares__team=self.object).order_by('-shares__shared_at') return context class BookmarkShareToggleView(LoginRequiredMixin, View): def post(self, request, pk, team_id): bookmark = get_object_or_404(Bookmark, pk=pk, user=request.user) team = get_object_or_404(Team, pk=team_id, members=request.user) share, created = BookmarkShare.objects.get_or_create( bookmark=bookmark, team=team, defaults={'shared_by': request.user} ) if not created: share.delete() shared = False else: shared = True return JsonResponse({'shared': shared}) class UserActivityLogView(LoginRequiredMixin, ListView): model = LogEntry template_name = 'core/activity_log.html' context_object_name = 'log_entries' paginate_by = 20 def get_queryset(self): return LogEntry.objects.filter(actor=self.request.user).select_related('content_type', 'actor').order_by('-timestamp')