Add route to view in flask-admin - flask

so basically I'm trying to add a custom endpoint to my Flask + Flask-Admin app that allows admins to download a CSV.
Something like this:
class MyAdminIndexView(AdminIndexView):
#expose('/')
def index(self):
return self.render('admin/home.html')
def is_accessible(self):
return current_user.has_role('admin')
#expose('/csv-export')
def csv_export(self):
batch_num = request.args.get('batch_num')
if not batch_num:
flash('Invalid batch id', 'danger')
abort(404)
si = io.StringIO()
cw = csv.writer(si)
rows = MyObj.query.filter_by(batch_num=batch_num).all()
cw.writerows(rows)
output = make_response(si.getvalue())
output.headers["Content-Disposition"] = "attachment; filename=export.csv"
output.headers["Content-type"] = "text/csv"
return output
But if I add this to my base admin view, the '/csv-export' route doesn't register and I get a 404.
I guess I could add an entirely new Blueprint just for this endpoint but it seems like a lot of work for a simple endpoint that doesn't require a separate template view. Any tips?

Related

Can't Get Image from Django Api To React Native

Hello Friends I Can't Get Image From Django To React Native
Here My Code
Fetch APi
const [add, setStudents] = useState([{}])
async function getAllStudent() {
try {
const add = await axios.get('http://192.168.1.77:19000/api/add/')
method:'GET',
setStudents(add.data)
}
catch (error) {
console.log(error)
}
}
getAllStudent();
FlatList :
<FlatList
data={add}
renderItem={({item})=>
<Image
style={{width:200,height:200, backgroundColor:'green',}}
source={{uri:item.image}}
/>
}
/>
Django Code Is Here
Views
class addpost(viewsets.ViewSet):
def list(self,request):
postadd1 = postadd.objects.all()
postaddserial1 = postaddserial(postadd1,many=True)
return Response(postaddserial1.data)
def create(self,request):
postaddserial1 = postaddserial(data=request.data)
if postaddserial1.is_valid():
postaddserial1.save()
return Response(postaddserial1.data, status=status.HTTP_201_CREATED)
return Response(postaddserial1.errors, status=status.HTTP_400_BAD_REQUEST )
def retrieve(self,request,pk=None):
queryset = postadd.objects.all()
contact = get_object_or_404(queryset,pk=pk)
postaddserial1 = postaddserial(contact)
return Response(postaddserial1.data)
def update(self,request,pk=None):
contact = postadd.objects.get(pk=pk)
postaddserial1 = postaddserial(contact,data=request.data)
if postaddserial1.is_valid():
postaddserial1.save()
return Response(postaddserial1.data,status=status.HTTP_201_CREATED)
return Response(postaddserial1.errors,status=status.HTTP_400_BAD_REQUEST)
def destroy(self, request, pk=None):
postadd1 = postadd.objects.get(pk=pk)
postadd1.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
Serializer
class postaddserial(serializers.ModelSerializer):
class Meta:
model = postadd
fields ='__all__'
Model
class postadd(models.Model):
city=models.CharField(max_length=122)
carinfo=models.CharField(max_length=122)
milage=models.CharField(max_length=122)
price=models.CharField(max_length=122)
company=models.CharField(max_length=122)
image = models.ImageField(upload_to ='static/Images', height_field=None, width_field=None, max_length=100,)
engine=models.CharField(max_length=122)
contact=models.CharField(max_length=122)
I make django Api to add product in react native. i show image from post man . but cant get image in react native from api
i am making application where i can add product from django rest framework and show it on react native through api i get all data in react native but cant get image from api. when i use postman i get image from api but when i use api on react native i can't get image
You need to define media settings and absolute url for image or files.
For more helo you can see here
Django serializer Imagefield to get full URL

Access a view which is for logged in users in django testing

Im trying to create some tests for my django project. I get multiple errors when trying to do the views tests. Most of my views depend on a user to be logged in and I cant find the way to log in . Im using the deafult django built-in AUTH system.
VIEW :
#login_required
def fields(request):
if request.user.profile.user_package == "Livestock":
raise PermissionDenied()
field_list = Field.objects.filter(user = request.user)
context = {
"title": "Fields",
"field_list" : field_list,
}
template = 'agriculture/fields.html'
return render(request, template, context)
TetCase:
class TestViews(TestCase):
#classmethod
#factory.django.mute_signals(signals.pre_save, signals.post_save, signals.pre_delete, signals.post_delete)
def setUpTestData(cls):
# Create new user
test_user = User.objects.create(username='test_user',password='1XISRUkwtuK')
test_user.save()
c = Client()
profile = Profile.objects.get_or_create(user = test_user, user_package = 'hybrid')
c.login(username = test_user.username, password = test_user.password)
Field.objects.create(user=test_user,friendly_name='Arnissa')
def test_logged_in_user(self):
login = self.client.login(username='test_user', password='1XISRUkwtuK')
response = self.client.get(reverse('agriculture:fields'))
# Check our user is logged in
self.assertEqual(str(response.context['user']), 'test_user')
# Check that we got a response "success"
self.assertEqual(response.status_code, 200)
path: path('fields', views.fields, name='fields')
and settings if they provide any help :
LOGIN_REDIRECT_URL = 'dashboard:index'
LOGOUT_REDIRECT_URL = 'login'
LOGIN_URL = 'login'
On my tests i get the error TypeError: 'NoneType' object is not subscriptable when im checking if user is logged in. If i try to get the response I get AssertionError: 302 != 200.
When creating User in Django you should not use create method of user manager, because that sets the password to plain text instead of enrypting it. Try creating user using create_user method, like that:
test_user = User.objects.create_user(username='test_user',password='1XISRUkwtuK')
And if you don't want to use this method, you can always use force_login client method to login user without having to specify login nor password like that:
c.force_login(test_user)

Problem with testing custom admin actions

Running into some problems when testing my custom admin actions.
First I can show you an example of a test that works and the actions it's testing.
custom action, Product model
#admin.action(description="Merge selected products")
def merge_products(self, request, queryset):
list_of_products = queryset.order_by("created_at")
list_of_products = list(list_of_products)
canonical_product = list_of_products[0]
list_of_products.remove(canonical_product)
for p in list_of_products:
for ep in p.external_products.all():
ep.internal_product = canonical_product
ep.save()
p.save()
canonical_product.save()
related test
class MockRequest(object):
pass
class ProductAdminTest(TestCase):
def setUp(self):
self.product_admin = ProductAdmin(model=Product, admin_site=AdminSite())
User = get_user_model()
admin_user = User.objects.create_superuser(
username="superadmin", email="superadmin#email.com", password="testpass123"
)
self.client.force_login(admin_user)
def test_product_merge(self):
self.boot1 = baker.make("products.Product", title="Filippa K boots", id=uuid4())
self.boot2 = baker.make("products.Product", title="Filippa K boots", id=uuid4())
self.external_product1 = baker.make(
"external_products.ExternalProduct", internal_product=self.boot1
)
self.external_product2 = baker.make(
"external_products.ExternalProduct", internal_product=self.boot2
)
self.assertEqual(self.boot1.external_products.count(), 1)
self.assertEqual(self.boot2.external_products.count(), 1)
request = MockRequest()
queryset = Product.objects.filter(title="Filippa K boots")
self.product_admin.merge_products(request, queryset)
self.assertEqual(self.boot1.external_products.count(), 2)
self.assertEqual(self.boot2.external_products.count(), 0)
Might not be the pretties test but it works, so does the action.
The code above works as it should but has been running into problems when trying to test an almost identical action but for another model.
custom action, Brand model
#admin.action(description="Merge selected brands")
def merge_brands(self, request, queryset):
qs_of_brands = queryset.order_by("created_at")
list_of_brands = list(qs_of_brands)
canonical_brand = list_of_brands[0]
for brand in list_of_brands:
if brand.canonical:
canonical_brand = brand
list_of_brands.remove(canonical_brand)
for brand in list_of_brands:
brand.canonical_brand = canonical_brand
brand.save()
for product in Product.objects.filter(brand=brand):
product.brand = canonical_brand
product.save()
canonical_brand.save()
related test
class MockRequest(object):
pass
def setUp(self):
self.brand_admin = BrandAdmin(model=Brand, admin_site=AdminSite())
User = get_user_model()
admin_user = User.objects.create_superuser(
username="superadmin", email="superadmin#email.com", password="testpass123"
)
self.client.force_login(admin_user)
def test_brand_merge(self):
self.brand1 = baker.make("brands.Brand", title="Vans")
self.brand1.canonical_brand = self.brand1
self.brand1.active = True
self.brand1.save()
self.brand2 = baker.make("brands.Brand", title="Colmar")
self.boot1 = baker.make("products.Product", brand=self.brand1, id=uuid4())
self.boot2 = baker.make("products.Product", brand=self.brand2, id=uuid4())
self.assertEqual(self.boot1.brand, self.brand1)
self.assertEqual(self.boot2.brand, self.brand2)
self.assertEqual(self.brand1.active, True)
self.assertEqual(self.brand2.active, False)
request = MockRequest()
queryset = Brand.objects.all()
self.brand_admin.merge_brands(request, queryset)
self.assertEqual(self.boot1.brand, self.brand1)
self.assertEqual(self.boot2.brand, self.brand1)
self.assertEqual(self.brand1.active, True)
self.assertEqual(self.brand2.active, False)
self.assertEqual(self.brand1.canonical_brand, self.brand1)
self.assertEqual(self.brand2.canonical_brand, self.brand1)
It's not really necessary how the code above works but I included it for context.
The product admin action works both when I try it manually and in the test suite. The brand action does work as it should when tested manually but in the test suite, it does nothing.
self.brand_admin.merge_brands(request, queryset)
does not change the tested objects in any way.
I have another custom admin action for the Brand model and it's the same problem there, works like a charm when I try it manually but it does nothing in the test suite. Because of those facts, my guess is that the problem is related to my admin setup in the test suite but it's identical to the admin setup used for the first test which works.
Any ideas?
Gladly provide more context if needed.
It looks like you need to refresh the objects from the Database, in your test self.brand1, boot1, etc are in memory and will not be automatically updated from the database, you need to get the new values from the database.
https://docs.djangoproject.com/en/4.0/ref/models/instances/#refreshing-objects-from-database
If you need to reload a model’s values from the database, you can use the refresh_from_db() method. When this method is called without arguments the following is done:
All non-deferred fields of the model are updated to the values currently present in the database.
Any cached relations are cleared from the reloaded instance.
After you call your merge_brands you should refresh each object.
self.boot1.refresh_from_db()
self.boot2.refresh_from_db()
# etc..

Publish a custom Django Flatpage at a set date and time

I have a custom Flatpage model:
from django.contrib.flatpages.models import FlatPage
class MyFlatPage(FlatPage):
publish = models.DateTimeField()
so that I can add a publish date in the future.
Now, I don't have a proper list of flatpages on the front end, my use for frontpages is more like 'one-offs', where I specific the URL and all that. For example, 'about', '2019prize', 'Today's walk', stuff like that.
The urls.py is set up to catch all the flatpages with:
from django.contrib.flatpages import views
re_path(r'^(?P<url>.*/)$', views.flatpage)
How can I set these pages I create to be displayed only after the publish date has arrived? I know that I can filter them by looking up something like pages.filter(publish__lte=now). Where and how should I put that code though?
Additional information
I suppose I need to create a custom view, is that correct? The original view is in ../lib/python3.8/site-packages/django/contrib/flatpages/views.py:
def flatpage(request, url)
if not url.startswith('/'):
url = '/' + url
site_id = get_current_site(request).id
try:
f = get_object_or_404(FlatPage, url=url, sites=site_id)
except Http404:
if not url.endswith('/') and settings.APPEND_SLASH:
url += '/'
f = get_object_or_404(FlatPage, url=url, sites=site_id)
return HttpResponsePermanentRedirect('%s/' % request.path)
else:
raise
return render_flatpage(request, f)
#csrf_protect
def render_flatpage(request, f):
if f.registration_required and not request.user.is_authenticated:
from django.contrib.auth.views import redirect_to_login
return redirect_to_login(request.path)
if f.template_name:
template = loader.select_template((f.template_name, DEFAULT_TEMPLATE))
else:
template = loader.get_template(DEFAULT_TEMPLATE)
f.title = mark_safe(f.title)
f.content = mark_safe(f.content)
return HttpResponse(template.render({'flatpage': f}, request))
How can I extend this, adding my if publish__lte=now code?
What I did is copy-paste the view code from ../lib/python3.8/site-packages/django/contrib/flatpages/views.py to my app.views, rename the two functions, and add the following to render_myflatpage:
def render_myflatpage(request, f):
[...]
if f.publish > now:
f.content = 'This content will be published on ' + str(f.publish)
I then assigned the new view in the catch-all urls.py code:
re_path(r'^(?P<url>.*/)$', myflatpage)
I know this goes against the DRY protocol; this works for me for the time being. If there's a more elegant solution please do let me know.

Django Admin: how to display a url as a link while calling specific function to download the file

Title is a bit confusing, but basically I have an s3 path stored as a string
class S3Stuff(Model):
s3_path = CharField(max_length=255, blank=True, null=True)
# rest is not important
There are existing methods to download the content given the url, so I want to utilize that
def download_from_s3(bucket, file_name):
s3_client = boto3.client(bleh_bleh)
s3_response = s3_client.get_object(Bucket=s3_bucket, Key=file_name)
return {'response': 200, 'body': s3_response['Body'].read()}
s3_path can be broken into bucket and file_name. This works very easily when I use my own frontend because I can do whatever I want with it, but I don't know how to apply this to admin
class S3StuffAdmin(admin.StackedInline):
model = S3Stuff
fields = ('s3_path', )
Now how do I call that method and make the display a link that says "download"
I don't think this function will be much useful for generating download links, instead use the boto3's presigned_url like this:
from django.utils.html import format_html
class S3StuffAdmin(admin.StackedInline):
model = S3Stuff
fields = ('s3_path', )
readonly_field = ('download',)
def download(self, obj):
s3_client = boto3.client(bleh_bleh)
url = s3_client.generate_presigned_url('get_object', Params = {'Bucket': 'bucket', 'Key': obj.s3_path}, ExpiresIn = 100)
return format_html('<a href={}>download</a>'.format(url))