Skip to content

Commit

Permalink
ability to delete quality profiles (as long as they're not system-wid…
Browse files Browse the repository at this point in the history
…e defaults)
  • Loading branch information
lardbit committed Aug 15, 2024
1 parent e151826 commit d1865bc
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 7 deletions.
17 changes: 17 additions & 0 deletions src/frontend/src/app/api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,23 @@ export class ApiService {
);
}

public deleteQualityProfile(id: number): Observable<any> {
return this.http.delete(`${this.API_URL_QUALITY_PROFILES}${id}/`, {headers: this._requestHeaders()}).pipe(
map((data: any) => {
// remove this quality profile
this.qualityProfiles = this.qualityProfiles.filter(profile => profile.id !== id);
// unset quality profile from Movie/TVShow/TVSeasonRequest media records
[this.watchMovies, this.watchTVShows, this.watchTVSeasonRequests].forEach((watchMediaList) => {
watchMediaList.forEach((watchMedia) => {
if (watchMedia.quality_profile === id) {
watchMedia.quality_profile = null;
}
})
})
}),
);
}

public searchTorrents(query: string, mediaType: string): Observable<any> {
return this.http.get(`${this.API_URL_SEARCH_TORRENTS}?q=${query}&media_type=${mediaType}`, {headers: this._requestHeaders()}).pipe(
map((data: any) => {
Expand Down
7 changes: 5 additions & 2 deletions src/frontend/src/app/settings/quality-profiles.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,11 @@ <h4 class="modal-title">Quality Profiles</h4>
Require 5.1 Surround Sound
</label>
</div>
<div *ngIf="{'valid': form.controls.profiles.controls[i].valid} as validity" class="text-end">
<button type="button" class="btn" [ngClass]="{'btn-outline-success': validity.valid, 'btn-outline-danger': !validity.valid}" (click)="save(form.controls.profiles.controls[i])" [disabled]="!validity.valid">Save</button>
<div class="my-2 text-end">
<button type="button" class="btn btn-outline-danger me-2" (click)="delete(i)">Delete</button>
<ng-container *ngIf="{'ok': form.controls.profiles.controls[i].valid} as validity">
<button type="button" class="btn" [ngClass]="{'btn-success': validity.ok, 'btn-danger': !validity.ok}" (click)="save(form.controls.profiles.controls[i])" [disabled]="!validity.ok">Save</button>
</ng-container>
</div>
</div>
</div>
Expand Down
24 changes: 24 additions & 0 deletions src/frontend/src/app/settings/quality-profiles.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,28 @@ export class QualityProfilesComponent implements OnInit {
}
})
}

public delete(formArrayIndex: number) {
const profileFormGroup = this.form.controls.profiles.controls[formArrayIndex];
this.isLoading = true;
const data = profileFormGroup.value;
this.apiService.deleteQualityProfile(data.id).subscribe({
next: () => {
// remove form group
this.form.controls.profiles.removeAt(formArrayIndex);
this.toastr.success('Successfully deleted quality profile');
this.isLoading = false;
},
error: (error) => {
// display specific error message if it exists
if (error?.error?.message) {
this.toastr.error(error.error.message);
} else {
this.toastr.error('An unknown error occurred deleting the quality profile');
}
console.error(error);
this.isLoading = false;
}
})
}
}
4 changes: 2 additions & 2 deletions src/nefarious/api/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@

class IsAuthenticatedDjangoObjectUser(IsAuthenticated):
"""
User must be authenticated and updating object they "own"
User must be authenticated and updating an object they "own"
"""

def has_object_permission(self, request, view, obj):

# update operation, not staff, and the requesting user differs from the object's user
# non-staff/non-safe operation and the requesting user differs from the object's user
if request.method not in SAFE_METHODS:
if not request.user.is_staff and getattr(obj, 'user', None) and request.user != obj.user:
return False
Expand Down
16 changes: 16 additions & 0 deletions src/nefarious/api/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,9 +216,25 @@ def get_queryset(self):

@method_decorator(gzip_page, name='dispatch')
class QualityProfileViewSet(viewsets.ModelViewSet):
permission_classes = (IsAdminUser,)
queryset = QualityProfile.objects.all()
serializer_class = QualityProfileSerializer

def destroy(self, request, *args, **kwargs):
# prevent the deletion of the default tv/movies profiles in NefariousSettings
nefarious_settings = NefariousSettings.get()
if self.get_object() in [nefarious_settings.quality_profile_tv, nefarious_settings.quality_profile_movies]:
media_type = ''
if self.get_object() == nefarious_settings.quality_profile_tv:
media_type = 'tv'
elif self.get_object() == nefarious_settings.quality_profile_movies:
media_type = 'movies'
raise ValidationError({
'success': False,
'message': f"Cannot delete profile '{self.get_object()}' since it's used as a system-wide default for {media_type}",
})
return super().destroy(request, *args, **kwargs)


class TorrentBlacklistViewSet(viewsets.ModelViewSet):
queryset = TorrentBlacklist.objects.all()
Expand Down
29 changes: 29 additions & 0 deletions src/nefarious/migrations/0091_auto_20240815_2159.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 3.0.2 on 2024-08-15 21:59

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('nefarious', '0090_auto_20240812_2209'),
]

operations = [
migrations.AlterField(
model_name='watchmovie',
name='quality_profile',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='nefarious.QualityProfile'),
),
migrations.AlterField(
model_name='watchtvseasonrequest',
name='quality_profile',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='nefarious.QualityProfile'),
),
migrations.AlterField(
model_name='watchtvshow',
name='quality_profile',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='nefarious.QualityProfile'),
),
]
6 changes: 3 additions & 3 deletions src/nefarious/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ class WatchMovie(WatchMediaBase):
tmdb_movie_id = models.IntegerField(unique=True)
name = models.CharField(max_length=255)
poster_image_url = models.CharField(max_length=1000)
quality_profile = models.ForeignKey(QualityProfile, on_delete=models.CASCADE, null=True)
quality_profile = models.ForeignKey(QualityProfile, on_delete=models.SET_NULL, null=True)

class Meta:
ordering = ('name',)
Expand All @@ -176,7 +176,7 @@ class WatchTVShow(models.Model):
release_date = models.DateField(null=True, blank=True)
auto_watch = models.BooleanField(default=False) # whether to automatically watch future seasons
auto_watch_date_updated = models.DateField(null=True, blank=True) # date auto watch requested/updated
quality_profile = models.ForeignKey(QualityProfile, on_delete=models.CASCADE, null=True)
quality_profile = models.ForeignKey(QualityProfile, on_delete=models.SET_NULL, null=True)


class Meta:
Expand All @@ -200,7 +200,7 @@ class WatchTVSeasonRequest(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
watch_tv_show = models.ForeignKey(WatchTVShow, on_delete=models.CASCADE)
season_number = models.IntegerField()
quality_profile = models.ForeignKey(QualityProfile, on_delete=models.CASCADE, null=True)
quality_profile = models.ForeignKey(QualityProfile, on_delete=models.SET_NULL, null=True)
collected = models.BooleanField(default=False)
date_added = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
Expand Down

0 comments on commit d1865bc

Please sign in to comment.